diff --git a/.changeset/afraid-boxes-rhyme.md b/.changeset/afraid-boxes-rhyme.md new file mode 100644 index 0000000000..328a589f7e --- /dev/null +++ b/.changeset/afraid-boxes-rhyme.md @@ -0,0 +1,9 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-home': patch +--- + +Add `FavoriteToggle` in `core-components` to standardise favorite marking diff --git a/.changeset/afraid-fans-cross.md b/.changeset/afraid-fans-cross.md new file mode 100644 index 0000000000..1901d8ce0f --- /dev/null +++ b/.changeset/afraid-fans-cross.md @@ -0,0 +1,8 @@ +--- +'@backstage/create-app': patch +'@backstage/cli': patch +--- + +Add `config.d.ts` files to the list of included file in `tsconfig.json`. + +This allows ESLint to detect issues or deprecations in those files. diff --git a/.changeset/angry-hotels-warn.md b/.changeset/angry-hotels-warn.md new file mode 100644 index 0000000000..07caaba574 --- /dev/null +++ b/.changeset/angry-hotels-warn.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': minor +'@backstage/backend-common': minor +--- + +**BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. diff --git a/.changeset/beige-jobs-begin.md b/.changeset/beige-jobs-begin.md new file mode 100644 index 0000000000..f93e534857 --- /dev/null +++ b/.changeset/beige-jobs-begin.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Remove direct `vite` dependency diff --git a/.changeset/breezy-guests-scream.md b/.changeset/breezy-guests-scream.md new file mode 100644 index 0000000000..e33344f992 --- /dev/null +++ b/.changeset/breezy-guests-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-test-utils': patch +--- + +The extension tester will no longer unconditionally enable any additional extensions that have been added. diff --git a/.changeset/bright-pillows-confess.md b/.changeset/bright-pillows-confess.md new file mode 100644 index 0000000000..aa7210ffb8 --- /dev/null +++ b/.changeset/bright-pillows-confess.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Removed default title set to Unknown page on `ContentHeaderTitle` component to support usage of component without title prop. diff --git a/.changeset/brown-queens-lick.md b/.changeset/brown-queens-lick.md new file mode 100644 index 0000000000..37a4b1ec93 --- /dev/null +++ b/.changeset/brown-queens-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Use getOptionalString for optional app.support.items[].links[].title diff --git a/.changeset/calm-toes-melt.md b/.changeset/calm-toes-melt.md new file mode 100644 index 0000000000..b8a7b12c46 --- /dev/null +++ b/.changeset/calm-toes-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +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. diff --git a/.changeset/chilled-cameras-change.md b/.changeset/chilled-cameras-change.md new file mode 100644 index 0000000000..8491761594 --- /dev/null +++ b/.changeset/chilled-cameras-change.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Internal update to use the new cache manager diff --git a/.changeset/chilly-birds-shout.md b/.changeset/chilly-birds-shout.md new file mode 100644 index 0000000000..dfa99b6f5d --- /dev/null +++ b/.changeset/chilly-birds-shout.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-common': minor +--- + +**BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + +- The deprecated `dropDatabase` function has now been removed, without replacement. diff --git a/.changeset/cool-actors-sin.md b/.changeset/cool-actors-sin.md new file mode 100644 index 0000000000..4d5b2589c5 --- /dev/null +++ b/.changeset/cool-actors-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +--- + +Throw correct error when email is missing from the claims diff --git a/.changeset/create-app-1725976494.md b/.changeset/create-app-1725976494.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1725976494.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/cuddly-glasses-battle.md b/.changeset/cuddly-glasses-battle.md new file mode 100644 index 0000000000..be1d1a27e3 --- /dev/null +++ b/.changeset/cuddly-glasses-battle.md @@ -0,0 +1,7 @@ +--- +'@backstage/integration-react': patch +--- + +Remove unnecessary broad permissions from Gitlab `SCMAuth` + +Newer versions of Gitlab (after 2019) do not require the broad api permissions to write to repos. diff --git a/.changeset/dependabot-b42f054.md b/.changeset/dependabot-b42f054.md new file mode 100644 index 0000000000..b7f203ed9e --- /dev/null +++ b/.changeset/dependabot-b42f054.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-app-api': patch +'@backstage/backend-common': patch +'@backstage/backend-defaults': patch +--- + +chore(deps): bump `path-to-regexp` from 6.2.2 to 8.0.0 diff --git a/.changeset/dry-glasses-push.md b/.changeset/dry-glasses-push.md new file mode 100644 index 0000000000..31a04db24a --- /dev/null +++ b/.changeset/dry-glasses-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': patch +--- + +Extend the "unable to resolve user identity" message diff --git a/.changeset/early-cats-deliver.md b/.changeset/early-cats-deliver.md new file mode 100644 index 0000000000..77811444dc --- /dev/null +++ b/.changeset/early-cats-deliver.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Support menu items should not be buttons in favour of links diff --git a/.changeset/early-swans-grab.md b/.changeset/early-swans-grab.md new file mode 100644 index 0000000000..4526067275 --- /dev/null +++ b/.changeset/early-swans-grab.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +'@backstage/plugin-user-settings-backend': patch +'@backstage/plugin-devtools-backend': patch +--- + +Update README installation instructions diff --git a/.changeset/fair-peaches-learn.md b/.changeset/fair-peaches-learn.md new file mode 100644 index 0000000000..772980ee57 --- /dev/null +++ b/.changeset/fair-peaches-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Allow listing file contents with `debug:log` scaffolder action diff --git a/.changeset/fair-seals-burn.md b/.changeset/fair-seals-burn.md new file mode 100644 index 0000000000..51cdacbc38 --- /dev/null +++ b/.changeset/fair-seals-burn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': patch +--- + +Accepts an optional options object in the `PassportOAuthAuthenticatorHelper.authenticate` method. diff --git a/.changeset/fast-experts-grin.md b/.changeset/fast-experts-grin.md new file mode 100644 index 0000000000..ef5f5b2488 --- /dev/null +++ b/.changeset/fast-experts-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Migrated the `Bitbucket Server` auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-bitbucket-server-provider` module. diff --git a/.changeset/few-icons-end.md b/.changeset/few-icons-end.md new file mode 100644 index 0000000000..5c8556d09d --- /dev/null +++ b/.changeset/few-icons-end.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Expose styles for TemplateEditor, TemplateFormPreviewer and CustomFieldExplorer diff --git a/.changeset/five-walls-carry.md b/.changeset/five-walls-carry.md new file mode 100644 index 0000000000..34b5fb75cb --- /dev/null +++ b/.changeset/five-walls-carry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Severity filter label newly contains "Min severity" to better describe range instead of exact value. diff --git a/.changeset/forty-eyes-return.md b/.changeset/forty-eyes-return.md new file mode 100644 index 0000000000..b3d8355702 --- /dev/null +++ b/.changeset/forty-eyes-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Update Scaffolder module template to add itself to the backend diff --git a/.changeset/funny-rocks-pull.md b/.changeset/funny-rocks-pull.md new file mode 100644 index 0000000000..9145464137 --- /dev/null +++ b/.changeset/funny-rocks-pull.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': minor +'@backstage/plugin-scaffolder': minor +--- + +Added support for `omitExtraData` and `liveOmit` for rjsf in the scaffolder diff --git a/.changeset/fuzzy-feet-exist.md b/.changeset/fuzzy-feet-exist.md new file mode 100644 index 0000000000..35ef1e58fd --- /dev/null +++ b/.changeset/fuzzy-feet-exist.md @@ -0,0 +1,34 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/plugin-app': patch +--- + +Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + +You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + +```ts +// Before +createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], +}); + +// After +createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], +}); +``` diff --git a/.changeset/fuzzy-fireants-crash.md b/.changeset/fuzzy-fireants-crash.md new file mode 100644 index 0000000000..1feec6232c --- /dev/null +++ b/.changeset/fuzzy-fireants-crash.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Added scaffolder action publish:bitbucketCloud:pull-request diff --git a/.changeset/gentle-hats-act.md b/.changeset/gentle-hats-act.md new file mode 100644 index 0000000000..e78076c9c5 --- /dev/null +++ b/.changeset/gentle-hats-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Added a new `CreateAppOptions` type for the `createApp` options. diff --git a/.changeset/great-zebras-hunt.md b/.changeset/great-zebras-hunt.md new file mode 100644 index 0000000000..252f270092 --- /dev/null +++ b/.changeset/great-zebras-hunt.md @@ -0,0 +1,7 @@ +--- +'@backstage/cli': patch +--- + +The app build process now outputs an additional `index.html.tmpl` file. This is an non-templated version of the `index.html` file, which can be used to delay templating until runtime. + +The new `index.html.tmpl` file also sets a `backstage-public-path` meta tag to be templated at runtime. The meta tag is in turn picked up by the new `@backstage/cli/config/webpack-public-path.js` entry point script, which uses it to set the runtime public path of the Webpack bundle. diff --git a/.changeset/green-pumas-build.md b/.changeset/green-pumas-build.md new file mode 100644 index 0000000000..6f0a4c9773 --- /dev/null +++ b/.changeset/green-pumas-build.md @@ -0,0 +1,11 @@ +--- +'@backstage/backend-plugin-api': major +'@backstage/backend-app-api': major +'@backstage/backend-test-utils': major +--- + +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 https://github.com/backstage/backstage/issues/26353 or consult the [migration guide](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating/#remove-support-for-the-old-backend-system). diff --git a/.changeset/happy-bags-invite.md b/.changeset/happy-bags-invite.md new file mode 100644 index 0000000000..829d7a0e49 --- /dev/null +++ b/.changeset/happy-bags-invite.md @@ -0,0 +1,9 @@ +--- +'@backstage/core-components': patch +'@backstage/app-defaults': patch +'@backstage/plugin-catalog-react': patch +'@backstage/core-app-api': patch +'@backstage/test-utils': patch +--- + +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). diff --git a/.changeset/hip-bottles-hear.md b/.changeset/hip-bottles-hear.md new file mode 100644 index 0000000000..678cc3191c --- /dev/null +++ b/.changeset/hip-bottles-hear.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Migrated the `Auth0` auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-auth0-provider` module. diff --git a/.changeset/hip-fans-decide.md b/.changeset/hip-fans-decide.md new file mode 100644 index 0000000000..ecdf6d8a4d --- /dev/null +++ b/.changeset/hip-fans-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-node': patch +--- + +Add definition for the new `backstage.inline` field in `package.json`. diff --git a/.changeset/hip-poems-invent.md b/.changeset/hip-poems-invent.md new file mode 100644 index 0000000000..c9ec139bdd --- /dev/null +++ b/.changeset/hip-poems-invent.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-client': minor +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog': minor +--- + +Allow offset mode paging in entity list provider diff --git a/.changeset/khaki-clocks-own.md b/.changeset/khaki-clocks-own.md new file mode 100644 index 0000000000..b60de7a1ba --- /dev/null +++ b/.changeset/khaki-clocks-own.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Make MySQL pool settings a bit more lax diff --git a/.changeset/khaki-crews-rule.md b/.changeset/khaki-crews-rule.md new file mode 100644 index 0000000000..4d8eb32083 --- /dev/null +++ b/.changeset/khaki-crews-rule.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +**BREAKING**: The app backend now supports the new `index.html.tmpl` output from `@backstage/cli`. If available, the `index.html` will be templated at runtime with the current configuration of the app backend. + +This is marked as a breaking change because you must now supply the app build-time configuration to the backend. This change also affects the public path behavior, where it is no longer necessary to build the app with the correct public path upfront. You now only need to supply a correct `app.baseUrl` to the app backend plugin at runtime. + +An effect that this change has is that the `index.html` will now contain and present the frontend configuration in an easily readable way, which can aid in debugging. This data was always available in the frontend, but it was injected and hidden in the static bundle. + +This templating behavior is enabled by default, but it can be disabled by setting the `app.disableConfigInjection` configuration option to `true`. diff --git a/.changeset/long-peas-repeat.md b/.changeset/long-peas-repeat.md new file mode 100644 index 0000000000..1ea65056b4 --- /dev/null +++ b/.changeset/long-peas-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Add support for mapping custom tags in the techdocs yaml parser that validates the mkdocs.yaml file diff --git a/.changeset/loud-singers-happen.md b/.changeset/loud-singers-happen.md new file mode 100644 index 0000000000..04207d6428 --- /dev/null +++ b/.changeset/loud-singers-happen.md @@ -0,0 +1,16 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Add the `relations` array to allow Backstage to mirror GitLab's membership behavior, including descendant, inherited, and shared-from-group memberships. + +The previous `allowInherited` config option will be deprecated in future versions. Use the `relations` array with the `INHERITED` option instead. + +```yaml +catalog: + providers: + gitlab: + development: + relations: + - INHERITED +``` diff --git a/.changeset/odd-goats-kiss.md b/.changeset/odd-goats-kiss.md new file mode 100644 index 0000000000..0f199c1a1e --- /dev/null +++ b/.changeset/odd-goats-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Added `titleComponent` prop to `SignInPage` component to allow further customization of the title using `ReactNode` diff --git a/.changeset/odd-insects-behave.md b/.changeset/odd-insects-behave.md new file mode 100644 index 0000000000..c0e5217a29 --- /dev/null +++ b/.changeset/odd-insects-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Support button title should be contained in a single menu item diff --git a/.changeset/orange-shrimps-pull.md b/.changeset/orange-shrimps-pull.md new file mode 100644 index 0000000000..7d509a0e5d --- /dev/null +++ b/.changeset/orange-shrimps-pull.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-notifications': patch +--- + +Implement icon in backend and show icon in table if available. diff --git a/.changeset/orange-trainers-allow.md b/.changeset/orange-trainers-allow.md new file mode 100644 index 0000000000..e24b4e66ad --- /dev/null +++ b/.changeset/orange-trainers-allow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app': patch +--- + +Fix issue with `AlertDisplay` and other components defined with `AppRootElementBlueprint` not being rendered when at the `SignInWrapper` diff --git a/.changeset/plenty-dragons-know.md b/.changeset/plenty-dragons-know.md new file mode 100644 index 0000000000..2aa8458011 --- /dev/null +++ b/.changeset/plenty-dragons-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-defaults': patch +--- + +Added `createPublicSignInApp`, used to creating apps for the public entry point. diff --git a/.changeset/plenty-hounds-poke.md b/.changeset/plenty-hounds-poke.md new file mode 100644 index 0000000000..3d308d763a --- /dev/null +++ b/.changeset/plenty-hounds-poke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Added the ability for the actions `publish:github` and `github:repo:create` to take inputs for 'custom properties' for organization repositories. diff --git a/.changeset/popular-pumpkins-rush.md b/.changeset/popular-pumpkins-rush.md new file mode 100644 index 0000000000..ef73e80889 --- /dev/null +++ b/.changeset/popular-pumpkins-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Fix scaffolder review step issue where schema options are not handled for fields on multi-step templates. diff --git a/.changeset/pre.json b/.changeset/pre.json index 492877ea8a..51d023f2c5 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -187,85 +187,188 @@ "@backstage/plugin-user-settings": "0.8.11", "@backstage/plugin-user-settings-backend": "0.2.22", "@backstage/plugin-user-settings-common": "0.0.1", - "@backstage/plugin-app": "0.0.0" + "@backstage/plugin-app": "0.0.0", + "@backstage/frontend-defaults": "0.0.0", + "@internal/frontend": "0.0.0", + "@backstage/plugin-auth-backend-module-auth0-provider": "0.0.0", + "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "0.0.0" }, "changesets": [ + "afraid-boxes-rhyme", + "afraid-fans-cross", + "angry-hotels-warn", + "beige-jobs-begin", "big-spies-stare", "blue-forks-cry", "breezy-flowers-dance", + "breezy-guests-scream", + "bright-pillows-confess", "brown-actors-clap", "brown-boxes-arrive", + "brown-queens-lick", "brown-worms-sneeze", "calm-brooms-fail", + "calm-toes-melt", + "chilled-cameras-change", + "chilly-birds-shout", "cold-ways-protect", + "cool-actors-sin", "cool-melons-check", "cool-poems-hammer", + "create-app-1725976494", + "cuddly-glasses-battle", "cuddly-rocks-invent", "curly-brooms-raise", + "dependabot-b42f054", + "dry-beers-shake", + "dry-glasses-push", "dry-monkeys-mate", + "early-cats-deliver", + "early-swans-grab", "eighty-tables-hope", "empty-coats-sparkle", + "fair-peaches-learn", + "fair-seals-burn", + "famous-badgers-drop", + "fast-experts-grin", + "few-icons-end", "five-tigers-share", + "five-walls-carry", "flat-kangaroos-push", + "forty-eyes-return", "forty-toes-float", + "four-parents-buy", "four-ties-raise", "fresh-apes-dress", + "fresh-bears-prove", "fresh-pumas-clean", + "friendly-days-march", + "friendly-months-march", "funny-houses-rest", + "funny-rocks-pull", + "fuzzy-feet-exist", + "fuzzy-fireants-crash", "fuzzy-mails-walk", "fuzzy-spies-share", + "gentle-hats-act", "giant-jars-mix", + "gorgeous-scissors-wave", + "great-zebras-hunt", + "green-pumas-build", "green-worms-rescue", + "happy-bags-invite", "healthy-moons-drum", "heavy-suits-judge", + "hip-bottles-hear", + "hip-fans-decide", "hip-pandas-think", + "hip-poems-invent", + "khaki-clocks-own", + "khaki-crews-rule", "kind-moose-learn", "kind-walls-speak", "large-poets-check", + "long-peas-repeat", + "loud-brooms-pull", + "loud-singers-happen", "lucky-sheep-cover", "metal-garlics-fetch", "metal-rivers-grin", + "mighty-cheetahs-call", "mighty-days-kiss", "nasty-tigers-knock", "new-poets-unite", "ninety-mayflies-raise", + "odd-goats-kiss", + "odd-insects-behave", "olive-phones-sniff", + "orange-shrimps-pull", + "orange-trainers-allow", + "plenty-dragons-know", + "plenty-hounds-poke", "popular-cooks-camp", + "popular-pumpkins-rush", + "proud-forks-buy", "purple-cows-sing", "purple-glasses-tease", + "quick-poets-relax", + "quick-suns-swim", + "quiet-spies-clean", + "rare-dogs-deny", "rare-feet-melt", "real-pants-rule", + "red-squids-own", + "renovate-11b1a0c", + "renovate-233ed8f", + "renovate-2a3da15", + "renovate-53b7d25", + "renovate-546c524", + "renovate-6d3e04e", + "renovate-93d032c", + "renovate-94fd31b", + "renovate-a674163", + "renovate-b8911c2", + "renovate-c59c91d", + "rich-bees-tickle", + "rich-cheetahs-vanish", + "rotten-crabs-hear", + "rude-fireants-accept", "serious-cheetahs-help", + "serious-spies-knock", + "seven-beds-deliver", "sharp-fans-tan", "sharp-items-study", "sharp-mayflies-beg", + "shiny-ants-relax", + "shiny-carpets-worried", + "shiny-carpets-worry", "shiny-zoos-film", "short-moles-brush", "silent-eyes-lie", + "silent-oranges-trade", + "silent-worms-admire", + "silly-icons-agree", "six-goats-sort", "six-humans-guess", "sixty-rabbits-cheat", + "slimy-chefs-think", + "slimy-hornets-explode", + "smart-beers-give", + "smart-gifts-report", + "smart-owls-sell", "spicy-poems-hammer", "spicy-vans-eat", + "strange-snakes-agree", + "stupid-toes-complain", "sweet-cows-clean", + "swift-fishes-rush", "swift-garlics-mix", "swift-radios-enjoy", "swift-seahorses-share", + "tall-camels-march", "tame-hornets-shake", "tasty-pigs-vanish", "thick-walls-share", + "thirty-bottles-rest", + "three-socks-call", + "tiny-icons-sit", "tiny-waves-provide", "tough-peaches-kneel", "tricky-apricots-film", "twenty-clouds-melt", + "twenty-jobs-repeat", "twenty-queens-grow", + "two-islands-deny", "unlucky-cycles-clean", + "violet-apricots-smoke", "violet-jokes-tap", "warm-boxes-grab-2", "warm-boxes-grab", "wild-buses-notice", + "wise-forks-play", "wise-scissors-help", "witty-years-cry", - "yellow-bees-hope" + "yellow-bees-hope", + "yellow-panthers-decide" ] } diff --git a/.changeset/proud-forks-buy.md b/.changeset/proud-forks-buy.md new file mode 100644 index 0000000000..558d5d9078 --- /dev/null +++ b/.changeset/proud-forks-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +The `ui:options` for `OwnedEntityPicker` field are now passed to `EntityPicker`. This allows you to use any `ui:options` which `EntityPicker` accepts in the `OwnedEntityPicker` field including `allowArbitraryValues` and `defaultNamespace`. diff --git a/.changeset/quick-poets-relax.md b/.changeset/quick-poets-relax.md new file mode 100644 index 0000000000..159744233b --- /dev/null +++ b/.changeset/quick-poets-relax.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-search-react': patch +--- + +Internal update to match recent React types diff --git a/.changeset/quick-suns-swim.md b/.changeset/quick-suns-swim.md new file mode 100644 index 0000000000..6113253e22 --- /dev/null +++ b/.changeset/quick-suns-swim.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Accept `ConfigService` instead of `Config` in constructors/factories diff --git a/.changeset/quiet-spies-clean.md b/.changeset/quiet-spies-clean.md new file mode 100644 index 0000000000..ba277f4021 --- /dev/null +++ b/.changeset/quiet-spies-clean.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Fixed a bug in the `CatalogGraphPage` component where, after clicking on some nodes, clicking the back button would break the navigation. This issue caused the entire navigation to fail and behaved differently across various browsers. diff --git a/.changeset/rare-dogs-deny.md b/.changeset/rare-dogs-deny.md new file mode 100644 index 0000000000..4a215cabfd --- /dev/null +++ b/.changeset/rare-dogs-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-auth0-provider': minor +--- + +New module for `@backstage/plugin-auth-backend` that adds a Auth0 auth provider. diff --git a/.changeset/red-squids-own.md b/.changeset/red-squids-own.md new file mode 100644 index 0000000000..4f6a1635d6 --- /dev/null +++ b/.changeset/red-squids-own.md @@ -0,0 +1,11 @@ +--- +'@backstage/integration': minor +--- + +The Gerrit integration can now resolve Gitiles urls that point to the following +refs: + +- HEAD +- A SHA +- Tag +- Branch diff --git a/.changeset/renovate-11b1a0c.md b/.changeset/renovate-11b1a0c.md new file mode 100644 index 0000000000..73a955494c --- /dev/null +++ b/.changeset/renovate-11b1a0c.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `@module-federation/enhanced` to `^0.6.0`. diff --git a/.changeset/renovate-233ed8f.md b/.changeset/renovate-233ed8f.md new file mode 100644 index 0000000000..e210236282 --- /dev/null +++ b/.changeset/renovate-233ed8f.md @@ -0,0 +1,45 @@ +--- +'@backstage/app-defaults': patch +'@backstage/core-app-api': patch +'@backstage/core-compat-api': patch +'@backstage/core-components': patch +'@backstage/core-plugin-api': patch +'@backstage/dev-utils': patch +'@backstage/frontend-app-api': patch +'@backstage/frontend-defaults': patch +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/test-utils': patch +'@backstage/theme': patch +'@backstage/version-bridge': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-app': patch +'@backstage/plugin-auth-react': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-catalog-unprocessed-entities': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-config-schema': patch +'@backstage/plugin-home': patch +'@backstage/plugin-kubernetes-cluster': patch +'@backstage/plugin-kubernetes-react': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-notifications': patch +'@backstage/plugin-org-react': patch +'@backstage/plugin-org': patch +'@backstage/plugin-permission-react': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-search': patch +'@backstage/plugin-signals-react': patch +'@backstage/plugin-signals': patch +'@backstage/plugin-techdocs-addons-test-utils': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +'@backstage/plugin-techdocs-react': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-user-settings': patch +--- + +Updated dependency `@testing-library/react` to `^16.0.0`. diff --git a/.changeset/renovate-2a3da15.md b/.changeset/renovate-2a3da15.md new file mode 100644 index 0000000000..3f612a07ae --- /dev/null +++ b/.changeset/renovate-2a3da15.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': patch +--- + +Updated dependency `typescript-json-schema` to `^0.65.0`. diff --git a/.changeset/renovate-546c524.md b/.changeset/renovate-546c524.md new file mode 100644 index 0000000000..bbe6d8afc5 --- /dev/null +++ b/.changeset/renovate-546c524.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Updated dependency `@useoptic/openapi-utilities` to `^0.55.0`. diff --git a/.changeset/renovate-6d3e04e.md b/.changeset/renovate-6d3e04e.md new file mode 100644 index 0000000000..558c0e78cd --- /dev/null +++ b/.changeset/renovate-6d3e04e.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Updated dependency `@node-saml/passport-saml` to `^5.0.0`. diff --git a/.changeset/renovate-94fd31b.md b/.changeset/renovate-94fd31b.md new file mode 100644 index 0000000000..6c6883c0d6 --- /dev/null +++ b/.changeset/renovate-94fd31b.md @@ -0,0 +1,38 @@ +--- +'@backstage/backend-app-api': patch +'@backstage/backend-common': patch +'@backstage/backend-defaults': patch +'@backstage/backend-openapi-utils': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-auth-backend-module-atlassian-provider': patch +'@backstage/plugin-auth-backend-module-bitbucket-provider': patch +'@backstage/plugin-auth-backend-module-github-provider': patch +'@backstage/plugin-auth-backend-module-gitlab-provider': patch +'@backstage/plugin-auth-backend-module-google-provider': patch +'@backstage/plugin-auth-backend-module-microsoft-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-provider': patch +'@backstage/plugin-auth-backend-module-oidc-provider': patch +'@backstage/plugin-auth-backend-module-okta-provider': patch +'@backstage/plugin-auth-backend-module-onelogin-provider': patch +'@backstage/plugin-auth-backend-module-pinniped-provider': patch +'@backstage/plugin-auth-backend-module-vmware-cloud-provider': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-node': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-permission-node': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-signals-backend': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-techdocs-node': patch +'@backstage/plugin-user-settings-backend': patch +--- + +Updated dependency `supertest` to `^7.0.0`. diff --git a/.changeset/renovate-a674163.md b/.changeset/renovate-a674163.md new file mode 100644 index 0000000000..396139042a --- /dev/null +++ b/.changeset/renovate-a674163.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-home-react': patch +'@backstage/plugin-home': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Updated dependency `@rjsf/utils` to `5.20.1`. +Updated dependency `@rjsf/core` to `5.20.1`. +Updated dependency `@rjsf/material-ui` to `5.20.1`. +Updated dependency `@rjsf/validator-ajv8` to `5.20.1`. diff --git a/.changeset/renovate-b8911c2.md b/.changeset/renovate-b8911c2.md new file mode 100644 index 0000000000..9f93a13254 --- /dev/null +++ b/.changeset/renovate-b8911c2.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Updated dependency `use-immer` to `^0.10.0`. diff --git a/.changeset/renovate-c59c91d.md b/.changeset/renovate-c59c91d.md new file mode 100644 index 0000000000..eede7c81c2 --- /dev/null +++ b/.changeset/renovate-c59c91d.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-explore': patch +--- + +Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.5`. diff --git a/.changeset/rich-cheetahs-vanish.md b/.changeset/rich-cheetahs-vanish.md new file mode 100644 index 0000000000..26cc55b917 --- /dev/null +++ b/.changeset/rich-cheetahs-vanish.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': patch +'@backstage/plugin-techdocs-node': patch +--- + +Internal fixes to match `testcontainers` update diff --git a/.changeset/rude-fireants-accept.md b/.changeset/rude-fireants-accept.md new file mode 100644 index 0000000000..0b543eddb2 --- /dev/null +++ b/.changeset/rude-fireants-accept.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +Add a `toJSON` on refs so that they can appear in expectations in jest tests diff --git a/.changeset/seven-beds-deliver.md b/.changeset/seven-beds-deliver.md new file mode 100644 index 0000000000..dcc74fb889 --- /dev/null +++ b/.changeset/seven-beds-deliver.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +Update README to clarify location of additional tabs example diff --git a/.changeset/shiny-ants-relax.md b/.changeset/shiny-ants-relax.md new file mode 100644 index 0000000000..7908118fda --- /dev/null +++ b/.changeset/shiny-ants-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Upgrade to `vite@v5` diff --git a/.changeset/shiny-carpets-worried.md b/.changeset/shiny-carpets-worried.md new file mode 100644 index 0000000000..c0093b49aa --- /dev/null +++ b/.changeset/shiny-carpets-worried.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': minor +--- + +**BREAKING**: The `namespace` parameter for API's is now defaulted to the `pluginId` which was discovered. This means that if you're overriding API's by using ID's directly, they might have changed to include the plugin ID too. diff --git a/.changeset/shiny-carpets-worry.md b/.changeset/shiny-carpets-worry.md new file mode 100644 index 0000000000..53688df920 --- /dev/null +++ b/.changeset/shiny-carpets-worry.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +'@backstage/frontend-app-api': patch +--- + +Removing deprecated `namespace` parameter in favour of `pluginId` instead diff --git a/.changeset/silent-oranges-trade.md b/.changeset/silent-oranges-trade.md new file mode 100644 index 0000000000..69d526817e --- /dev/null +++ b/.changeset/silent-oranges-trade.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-bitbucket-server-provider': minor +--- + +New module for `@backstage/plugin-auth-backend` that adds a `Bitbucket Server` auth provider. diff --git a/.changeset/silent-worms-admire.md b/.changeset/silent-worms-admire.md new file mode 100644 index 0000000000..c9e7e25ad9 --- /dev/null +++ b/.changeset/silent-worms-admire.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': minor +--- + +**BREAKING** Removed `createLifecycleMiddleware` and `LifecycleMiddlewareOptions` to clean up API surface. These exports have no external usage and do not provide value in its current form. If you were using these exports, please reach out to the maintainers to discuss your use case. diff --git a/.changeset/silly-icons-agree.md b/.changeset/silly-icons-agree.md new file mode 100644 index 0000000000..f002ddb1c6 --- /dev/null +++ b/.changeset/silly-icons-agree.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-org': patch +--- + +Updating docs to use `createFrontendModule` instead diff --git a/.changeset/slimy-chefs-think.md b/.changeset/slimy-chefs-think.md new file mode 100644 index 0000000000..7fa546a399 --- /dev/null +++ b/.changeset/slimy-chefs-think.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Refactor to use injected catalog client in the new backend system diff --git a/.changeset/slimy-hornets-explode.md b/.changeset/slimy-hornets-explode.md new file mode 100644 index 0000000000..7f29061ab3 --- /dev/null +++ b/.changeset/slimy-hornets-explode.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Removed duplicated titles on Scaffolder `TemplateListPage` component diff --git a/.changeset/smart-beers-give.md b/.changeset/smart-beers-give.md new file mode 100644 index 0000000000..5b9d9888ba --- /dev/null +++ b/.changeset/smart-beers-give.md @@ -0,0 +1,6 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +--- + +Internal refactor diff --git a/.changeset/smart-gifts-report.md b/.changeset/smart-gifts-report.md new file mode 100644 index 0000000000..fa3c8d96e9 --- /dev/null +++ b/.changeset/smart-gifts-report.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-unprocessed-entities': patch +--- + +Show additional info on DevTools unprocessed entities table + +- Location path (so that it's easier to search the failed entity from the YAML URL) +- Time info of last discovery and next refresh time so that users can be aware of it and can sort the errors based on the time. diff --git a/.changeset/strange-snakes-agree.md b/.changeset/strange-snakes-agree.md new file mode 100644 index 0000000000..f6dea9a44e --- /dev/null +++ b/.changeset/strange-snakes-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': minor +--- + +Allow using translations in `createDevApp` diff --git a/.changeset/stupid-toes-complain.md b/.changeset/stupid-toes-complain.md new file mode 100644 index 0000000000..246dce3af1 --- /dev/null +++ b/.changeset/stupid-toes-complain.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The build commands now support the new `backstage.inline` flag in `package.json`, which causes the contents of private packages to be inlined into the consuming package, rather than be treated as an external dependency. diff --git a/.changeset/swift-fishes-rush.md b/.changeset/swift-fishes-rush.md new file mode 100644 index 0000000000..ccfa3a0e40 --- /dev/null +++ b/.changeset/swift-fishes-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed an issue where published frontend packages would end up with an invalid import structure if a single module imported both `.css` and `.svg` files. diff --git a/.changeset/thirty-bottles-rest.md b/.changeset/thirty-bottles-rest.md new file mode 100644 index 0000000000..51b572d507 --- /dev/null +++ b/.changeset/thirty-bottles-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/eslint-plugin': patch +--- + +Added support for linting dependencies on workspace packages with the `backstage.inline` flag. diff --git a/.changeset/three-socks-call.md b/.changeset/three-socks-call.md new file mode 100644 index 0000000000..39b75f2fea --- /dev/null +++ b/.changeset/three-socks-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +The `factory` option is no longer required when overriding an extension. diff --git a/.changeset/twenty-jobs-repeat.md b/.changeset/twenty-jobs-repeat.md new file mode 100644 index 0000000000..1ea98319d7 --- /dev/null +++ b/.changeset/twenty-jobs-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Enhanced the table hover effect with a lighter color and updated the border radius to align with Backstage's theme styling diff --git a/.changeset/two-islands-deny.md b/.changeset/two-islands-deny.md new file mode 100644 index 0000000000..1ee2020b5e --- /dev/null +++ b/.changeset/two-islands-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add support for `backstage:^` version ranges to versions:bump when using the experimental yarn plugin diff --git a/.changeset/wise-forks-play.md b/.changeset/wise-forks-play.md new file mode 100644 index 0000000000..1c8be76d19 --- /dev/null +++ b/.changeset/wise-forks-play.md @@ -0,0 +1,17 @@ +--- +'@backstage/backend-defaults': minor +--- + +**BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + +For the `/database` subpath exports: + +- The deprecated `dropDatabase` function has now been removed, without replacement. +- The deprecated `LegacyRootDatabaseService` type has now been removed. +- The return type from `DatabaseManager.forPlugin` is now directly a `DatabaseService`, as arguably expected. +- `DatabaseManager.forPlugin` now requires the `deps` argument, with the logger and lifecycle services. + +For the `/cache` subpath exports: + +- The `PluginCacheManager` type has been removed. You can still import it from `@backstage/backend-common`, but it's deprecated there, and you should move off of that package by migrating fully to the new backend system. +- Accordingly, `CacheManager.forPlugin` immediately returns a `CacheService` instead of a `PluginCacheManager`. The outcome of this is that you no longer need to make the extra `.getClient()` call. The old `CacheManager` with the old behavior still exists on `@backstage/backend-common`, but the above recommendations apply. diff --git a/.changeset/yellow-panthers-decide.md b/.changeset/yellow-panthers-decide.md new file mode 100644 index 0000000000..a8bf76a17d --- /dev/null +++ b/.changeset/yellow-panthers-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Avoid generating API reports for packages with `backstage.inline` set. diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 72f7ee9fb4..12d33b450e 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -46,7 +46,7 @@ jobs: cat ${{ github.event_path }} > event.json - name: Upload Artifacts - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 with: name: preview-spec path: | diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 86db3795bc..9da484a497 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -61,7 +61,7 @@ jobs: uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Build and push - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.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 ac02794268..f07cf21784 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -8,14 +8,148 @@ permissions: contents: read jobs: - deploy-microsite-and-storybook: - permissions: - contents: write # for JamesIves/github-pages-deploy-action to push changes in repo + stable: runs-on: ubuntu-latest + concurrency: + group: stable-reference-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true env: CI: true NODE_OPTIONS: --max-old-space-size=8192 + + outputs: + release: ${{ steps.find-release.outputs.result }} + + steps: + - name: Harden Runner + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + + - name: find latest release + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + id: find-release + with: + script: | + const { data } = await github.rest.repos.listTags({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + }) + + const [{tag}] = data + .map(i => i.name) + .filter(tag => tag.match(/^v\d+\.\d+\.\d+$/)) + .map(tag => ({ + tag, + val: tag + .slice(1) + .split('.') + .reduce((val, part) => Number(val) * 1000 + Number(part)) + })) + .sort((a, b) => b.val - a.val) + + return tag + result-encoding: string + + - name: checkout latest release + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: refs/tags/${{ steps.find-release.outputs.result }} + + - name: use node.js 18.x + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: 18.x + registry-url: https://registry.npmjs.org/ # Needed for auth + + - name: yarn install + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 + with: + cache-prefix: ${{ runner.os }}-v18.x + + - name: build API reference + run: yarn build:api-docs + + - name: upload API reference + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 + with: + name: stable-reference + path: docs/reference/ + if-no-files-found: error + retention-days: 1 + + next: + runs-on: ubuntu-latest + concurrency: + group: next-reference-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: + CI: true + NODE_OPTIONS: --max-old-space-size=8192 + + steps: + - name: Harden Runner + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 + with: + egress-policy: audit + + - name: checkout master + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: use node.js 18.x + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: 18.x + registry-url: https://registry.npmjs.org/ # Needed for auth + + - name: yarn install + uses: backstage/actions/yarn-install@3c138326f7fcbf253b88170c1f29bae8e975d47c # v0.6.14 + with: + cache-prefix: ${{ runner.os }}-v18.x + + - name: build API reference + run: yarn build:api-docs + + - name: upload API reference + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 + with: + name: next-reference + path: docs/reference/ + if-no-files-found: error + retention-days: 1 + + # Also build and upload storybook + - name: storybook yarn install + run: yarn install --immutable + working-directory: storybook + + - name: storybook build + run: yarn build-storybook + working-directory: storybook + + - name: storybook upload + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4 + with: + name: storybook + path: storybook/dist/ + if-no-files-found: error + retention-days: 1 + + deploy-microsite-and-storybook: + permissions: + contents: write # for JamesIves/github-pages-deploy-action to push changes in repo + + runs-on: ubuntu-latest + + needs: + - stable + - next + + env: + CI: true + NODE_OPTIONS: --max-old-space-size=16384 DOCUSAURUS_SSR_CONCURRENCY: 5 concurrency: @@ -28,45 +162,65 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: use node.js 18.x uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version: 18.x registry-url: https://registry.npmjs.org/ # Needed for auth - # We avoid caching in this workflow, as we're running an install of both the top-level - # dependencies and the microsite. We leave it to the main master workflow to produce the - # cache, as that results in a smaller bundle. - - name: top-level yarn install - run: yarn install --immutable + # Stable docs + - name: checkout latest release + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: refs/tags/${{ needs.stable.outputs.release }} + - name: microsite yarn install run: yarn install --immutable working-directory: microsite - - name: storybook yarn install - run: yarn install --immutable - working-directory: storybook - - name: build API reference - run: yarn build:api-docs + - name: download stable reference + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + with: + name: stable-reference + path: docs/reference + + - name: generate stable docs + run: yarn docusaurus docs:version stable + working-directory: microsite + + - name: clear API reference + run: rm -r docs/reference + + # Next docs + - name: checkout master + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + clean: false + + - name: microsite yarn install + run: yarn install --immutable + working-directory: microsite + + - name: download next reference + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + with: + name: next-reference + path: docs/reference - name: build microsite run: yarn build working-directory: microsite - - name: build storybook - run: yarn build-storybook - working-directory: storybook - - - name: move storybook dist into microsite - run: mv storybook/dist/ microsite/build/storybook + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + with: + name: storybook + path: microsite/build/storybook - name: Check the build output run: ls microsite/build && ls microsite/build/storybook - name: Deploy both microsite and storybook to gh-pages - uses: JamesIves/github-pages-deploy-action@94f3c658273cf92fb48ef99e5fbc02bd2dc642b2 # v4.6.3 + uses: JamesIves/github-pages-deploy-action@920cbb300dcd3f0568dbc42700c61e2fd9e6139c # v4.6.4 with: branch: gh-pages folder: microsite/build diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index 27a5a78052..62bf70187b 100644 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ b/.github/workflows/pr-review-comment-trigger.yaml @@ -30,7 +30,7 @@ jobs: run: | mkdir -p ./pr echo $PR_NUMBER > ./pr/pr_number - - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: pr_number-${{ github.event.pull_request.number }} path: pr/ diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0d22deeca1..8196e61bb9 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -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@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.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@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 0369f89648..bd0a480ba5 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -29,7 +29,7 @@ jobs: cache-prefix: ${{ runner.os }}-v18.x - name: Create Snyk report - uses: snyk/actions/node@9213221444c2dc9e8b2502c1e857c26d851e84a7 # master + uses: snyk/actions/node@cdb760004ba9ea4d525f2e043745dfe85bb9077e # master continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found with: args: > diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 99fb8e16bd..75befb9959 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Monitor and Synchronize Snyk Policies - uses: snyk/actions/node@9213221444c2dc9e8b2502c1e857c26d851e84a7 # master + uses: snyk/actions/node@cdb760004ba9ea4d525f2e043745dfe85bb9077e # master with: command: monitor args: > @@ -46,7 +46,7 @@ jobs: # Above we run the `monitor` command, this runs the `test` command which is # the one that generates the SARIF report that we can upload to GitHub. - name: Create Snyk report - uses: snyk/actions/node@9213221444c2dc9e8b2502c1e857c26d851e84a7 # master + uses: snyk/actions/node@cdb760004ba9ea4d525f2e043745dfe85bb9077e # master continue-on-error: true # To make sure that SARIF upload gets called with: args: > @@ -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@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: snyk.sarif diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index ea31ea6431..177348c30f 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 # ℹ️ Command-line programs to run using the OS shell. # πŸ“š https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index b59c8c0dd1..e2788e2d47 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index ec58ba3dae..8663e420eb 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -56,7 +56,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: setup python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.10' diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index a695eb1d51..a290b221b2 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version: 18.x - - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5 with: python-version: '3.9' diff --git a/.gitignore b/.gitignore index 6db4bf549c..865918c1f7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ coverage # Bower dependency directory (https://bower.io/) bower_components +# Documentation reference, generated by build:api-docs +docs/reference + # node-waf configuration .lock-wscript diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f10116844b..c1a0283f09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Our vision for Backstage is for it to become the trusted standard toolbox (read: UX layer) for the open source infrastructure landscape. Think of it like Kubernetes for developer experience. We realize this is an ambitious goal. We can’t do it alone. -Therefore we want to create a strong community of contributors -- all working together to create the kind of delightful experience that our developers deserve. +Therefore, we want to create a strong community of contributors -- all working together to create the kind of delightful experience that our developers deserve. Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. ❀️ @@ -41,7 +41,7 @@ We encourage you to catch any accessibility issues already in the development ph ## Get Started! -So...feel ready to jump in? Let's do this. πŸ‘πŸ»πŸ’― +So... feel ready to jump in? Let's do this. πŸ‘πŸ»πŸ’― ### Cloning the Repository @@ -103,7 +103,7 @@ If you want to get a better understanding of the layout of the repo now that you ## Coding Guidelines -All code is formatted with `prettier` using the configuration in the repo. If possible we recommend configuring your editor to format automatically, but you can also use the `yarn prettier --write ` command to format files. +All code is formatted with `prettier` using the configuration in the repo. If possible, we recommend configuring your editor to format automatically, but you can also use the `yarn prettier --write ` command to format files. A consistent coding style is included via [EditorConfig](https://editorconfig.org/) with the file [`.editorconfig`](.editorconfig) at the root of the repo. Depending on your editor of choice, it will either support it out of the box or you can [download a plugin](https://editorconfig.org/#download) for the config to be applied. @@ -184,7 +184,7 @@ Changesets **are** needed for new packages, as that is what triggers the package ### Writing changesets -Changesets are an important part of the development process. They are used to generate Changelog entries for all changes to the project. Ultimately they are read by the end users to learn about important changes and fixes to the project. Some of these fixes might require manual intervention from users so it's important to write changesets that users understand and can take action on. +Changesets are an important part of the development process. They are used to generate Changelog entries for all changes to the project. Ultimately, they are read by the end users to learn about important changes and fixes to the project. Some of these fixes might require manual intervention from users so it's important to write changesets that users understand and can take action on. Here are some important do's and don'ts when writing changesets: @@ -274,7 +274,7 @@ These changes are **required** to `packages/backend/src/plugins/catalog.ts` As with other CNCF projects, Backstage has adopted a [Developers Certificate of Origin (DCO)](https://developercertificate.org/). A DCO is a lightweight way for a developer to certify that they wrote or otherwise have the right to submit code or documentation to a project. -To certify the code you submit to the repository you'll need to add a `Signed-off-by` line to your commits. +To certify the code you submit to the repository, you'll need to add a `Signed-off-by` line to your commits. `$ git commit -s -m 'Awesome commit message'` @@ -330,9 +330,9 @@ There are two ways you can do this: 1. You can run `yarn build:api-reports` from the root of the project and it will go through all of the existing API Reports and update them or create new ones as needed. This may take a while but is generally the best method if you are new to this. 2. You can run `yarn build:api-reports plugins/` from the root and it will update the existing API Report or create a new one. -> Note: the above commands assume you've run `yarn install` before hand or recently +> Note: the above commands assume you've run `yarn install` beforehand or recently. -Each plugin/package has its own API Report which means you might see more then one file updated or created depending on your changes. These changes will then need to be committed as well. +Each plugin/package has its own API Report which means you might see more than one file updated or created depending on your changes. These changes will then need to be committed as well. ## Submitting a Pull Request @@ -374,7 +374,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 of the various CI builds -Once these have been completed it's just a matter of being patient as the reviewers have time they will begin to review your PR. When the review 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, this task is also done by the maintainers. +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. ### Review Tips diff --git a/README.md b/README.md index 4cba1082b0..64a7b34b64 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,10 @@ To engage with our community, you can use the following resources: - [Backstage Community Sessions](https://github.com/backstage/community) - Join monthly meetups and explore Backstage community - Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star ❀️ +## Governance + +See the [GOVERNANCE.md](https://github.com/backstage/community/blob/main/GOVERNANCE.md) document in the [backstage/community](https://github.com/backstage/community) repository. + ## License Copyright 2020-2024 Β© 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 diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 90b1e2c7ef..0000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# This is generated by build:api-docs in the root -reference diff --git a/docs/accessibility/index.md b/docs/accessibility/index.md index da10d60dd2..021c8309be 100644 --- a/docs/accessibility/index.md +++ b/docs/accessibility/index.md @@ -52,7 +52,7 @@ Running this command will use the [Lighthouse config](https://github.com/backsta ::: -### Use Lighthouse Github Action on your own repo +### Use Lighthouse GitHub Action on your own repo If your Backstage plugin lives outside of the [Backstage main repository](https://github.com/backstage/backstage/), and you use GitHub Actions for continuous integration, we encourage you to add and modify the [Accessibility GitHub workflow](https://github.com/backstage/backstage/blob/master/.github/workflows/verify_accessibility.yml) to your needs. diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 44c00d1ab7..5df8fc334a 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -6,10 +6,7 @@ description: Backstage Utility APIs ## Introduction -Backstage Plugins strive to be self-contained, with as much functionality as -possible residing within the plugin itself and its backend APIs. There will -however always be a need for plugins to communicate outside of its boundaries, -both with other plugins and the app itself. +Backstage plugins strive to be self-contained, with as much functionality as possible residing within the plugin itself and its backend APIs. There will, however, always be a need for plugins to communicate outside of their boundaries, both with other plugins and with the app itself. Backstage provides two primary methods for plugins to communicate across their boundaries in client-side code. The first one being the @@ -61,7 +58,7 @@ Note that there is no explicit type given for embedded, and [`useApi`](../reference/core-plugin-api.useapi.md) is able to infer the type. -Also note that consuming Utility APIs is not limited to plugins, it can be done +Also note that consuming Utility APIs is not limited to plugins; it can be done from any component inside Backstage, including the ones in [`@backstage/core-plugin-api`](../reference/core-plugin-api.md). The only requirement is that they are beneath the `AppProvider` in the react tree. @@ -93,7 +90,7 @@ createApiFactory({ }); ``` -In this example the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) +In this example, the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) is our API, which encapsulates the [`ErrorApi`](../reference/core-plugin-api.errorapi.md) type. The [`alertApiRef`](../reference/core-plugin-api.alertapiref.md) is our single @@ -135,11 +132,11 @@ there is no step that needs to be taken to include these APIs in an app. ### Plugin APIs In addition to the core APIs, plugins can define and export their own APIs. -While doing so they should usually also provide default implementations of their -own APIs, for example, the `catalog` plugin exports `catalogApiRef`, and also +While doing so, they should usually also provide default implementations of their +own APIs; for example, the `catalog` plugin exports `catalogApiRef` and also supplies a default [`ApiFactory`](../reference/core-plugin-api.apifactory.md) of that API using the `CatalogClient`. There is one restriction to plugin-provided -API Factories: plugins may not supply factories for core APIs, trying to do so +API Factories: plugins may not supply factories for core APIs; trying to do so will cause the app to refuse to start. Plugins supply their APIs through the `apis` option of @@ -165,16 +162,16 @@ export const techdocsPlugin = createPlugin({ ### App APIs Lastly, the app itself is the final point where APIs can be added, and what has -the final say in what APIs will be loaded at runtime. The app may override the +the final say in what APIs will be loaded at runtime? The app may override the factories for any of the core or plugin APIs, with the exception of the config, app theme, and identity APIs. These are static APIs that are tied into the -[`createApp`](../reference/app-defaults.createapp.md) implementation, and +[`createApp`](../reference/app-defaults.createapp.md) implementation and therefore not possible to override. Overriding APIs is useful for apps that want to switch out behavior to tailor it -to their environment. In some cases plugins may also export multiple +to their environment. In some cases plugins, may also export multiple implementations of the same API, where they each have their own different -requirements on for example backend storage and surrounding environment. +requirements, for example, backend storage and the surrounding environment. Supplying APIs to the app works just like for plugins: @@ -238,21 +235,14 @@ checked by the type embedded in the ## Defining custom Utility APIs Plugins are free to define their own Utility APIs. Simply define the TypeScript -interface for the API, and create an +interface for the API and create an [`ApiRef`](../reference/core-plugin-api.apiref.md) using [`createApiRef`](../reference/core-plugin-api.createapiref.md) exported from -[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also be sure to -provide at least one implementation of the API, and to declare a default factory +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also, be sure to +provide at least one implementation of the API and to declare a default factory for the API in [`createPlugin`](../reference/core-plugin-api.createplugin.md). -Custom Utility APIs can be either public or private, which is up to the plugin -to choose. Private APIs do not expose an external API surface, and it's -therefore possible to make breaking changes to the API without affecting other -users of the plugin. If an API is made public however, it opens up for other -plugins to make use of the API, and it also makes it possible for users for your -plugin to override the API in the app. It is however important to maintain -backwards compatibility of public APIs, as you may otherwise break apps that are -using your plugin. +Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's therefore possible to make breaking changes to the API without affecting other users of the plugin. If an API is made public, however, it opens up for other plugins to make use of the API, and it also makes it possible for users for your plugin to override the API in the app. It is, however, important to maintain backward compatibility of public APIs, as you may otherwise break apps that are using your plugin. To make an API public, simply export the [`ApiRef`](../reference/core-plugin-api.apiref.md) of the API, and any associated diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index d1dad0b183..51424f1c10 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -1,13 +1,12 @@ --- id: add-auth-provider -title: Contributing New Providers +title: Contributing New Provider Modules description: Documentation on adding new authentication providers --- :::note Note -The primary audience for this documentation are contributors to the main -Backstage project that want to add support for new authentication providers. +The primary audience for this documentation are contributors that want to add support for new authentication providers. While you can follow it to implement your own custom providers it is much more advanced than using our built-in providers. @@ -122,191 +121,137 @@ due to its comprehensive set of supported authentication ### Quick guide -[1.](#installing-the-dependencies) Install the passport-js based provider -package. +[1.](#create-new-auth-provider-module) Create a new auth provider module -[2.](#create-implementation) Create a new folder structure for the provider. +[3.](#adding-an-oauth-based-provider) or [adding a proxy auth based provider](#creating-proxy-auth-based-provider) depending on your needs. -[3.](#adding-an-oauth-based-provider) Implement the provider, extending the -suitable framework if needed. +[4.](#add-the-provider-to-the-backend) Add the provider to the backend. -[4.](#hook-it-up-to-the-backend) Add the provider to the backend. +### Create new auth provider module -### Installing the dependencies: +In this example we will create auth module for a made up service named foobar. + +Create a new module using `yarn new`, pick `backend-module` and provide `auth-backend` as the plugin ID and `foobar-provider` as the module ID. + +Make sure that the module has the appropriate passport provider as a dependency. ```bash -cd plugins/auth-backend +cd plugins/auth-backend-backend-module-foobar-provider yarn add passport-provider-a yarn add @types/passport-provider-a ``` -### Create implementation - -Make a new folder with the name of the provider following the below file -structure: - -```bash -plugins/auth-backend/src/providers/providerA -β”œβ”€β”€ index.ts -└── provider.ts -``` - -**`plugins/auth-backend/src/providers/providerA/provider.ts`** defines the -provider class which implements a handler for the chosen framework. - ### Adding an OAuth based provider -If we're adding an `OAuth` based provider we would implement the -`OAuthHandlers` interface. By implementing this -interface we can use the `OAuthProvider` class provided by `lib/oauth`, meaning -we don't need to implement the full -`AuthProviderRouteHandlers` interface that providers -otherwise need to implement. +We're then creating a new module that can extend the Auth backend using the `authProvidersExtensionPoint`. -The provider class takes the provider's options as a class parameter. It also -imports the `Strategy` from the passport package. +```ts title="plugins/auth-backend-foobar-provider/src/module.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { providerAuthenticator } from './authenticator'; -```ts -import { Strategy as ProviderAStrategy } from 'passport-provider-a'; - -export type ProviderAProviderOptions = OAuthProviderOptions & { - // extra options here -} - -export class ProviderAAuthProvider implements OAuthHandlers { - private readonly _strategy: ProviderAStrategy; - - constructor(options: ProviderAProviderOptions) { - this._strategy = new ProviderAStrategy( - { - clientID: options.clientId, - clientSecret: options.clientSecret, - callbackURL: options.callbackUrl, - passReqToCallback: false, - response_type: 'code', - /// ... etc - } - verifyFunction, // See the "Verify Callback" section - ); - } - - async start() {} - async handler() {} -} -``` - -### Adding an non-OAuth based provider - -An non-`OAuth` based provider could implement -`AuthProviderRouteHandlers` instead. - -```ts -type ProviderAOptions = { - // ... -}; - -export class ProviderAAuthProvider implements AuthProviderRouteHandlers { - private readonly _strategy: ProviderAStrategy; - - constructor(options: ProviderAOptions) { - this._strategy = new ProviderAStrategy( - { - // ... +/** @public */ +export const authModuleFoobarProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'foobar', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, }, - verifyFunction, // See the "Verify Callback" section - ); - } - - async start() {} - async frameHandler() {} - async logout() {} - async refresh() {} // If supported -} -``` - -#### Integration Wrapper - -Each provider exports an object that provides a way to create new instances -of the provider, along with related utilities like predefined sign-in resolvers. - -The object is created using `createAuthProviderIntegration`, with the most -important part being the `create` method that acts as the factory function -for our provider. - -The factory should return an implementation of `AuthProviderFactory`, which -passes in a object with utilities for configuration, logging, token issuing, -etc. The factory should return an implementation of -`AuthProviderRouteHandlers`. - -The factory is what decides the mapping from -[static configuration](../conf/index.md) to the creation of auth providers. For -example, OAuth providers use `OAuthEnvironmentHandler` to allow for multiple -different configurations, one for each environment, which looks like this; - -```ts -export const okta = createAuthProviderIntegration({ - create(options?: { - /** - * The profile transformation function used to verify and convert the auth response - * into the profile that will be presented to the user. - */ - authHandler?: AuthHandler; - - /** - * Configure sign-in for this provider, without it the provider can not be used to sign users in. - */ - signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ - resolver: SignInResolver; - }; - }) { - return ({ providerId, globalConfig, config, resolverContext }) => - OAuthEnvironmentHandler.mapConfig(config, envConfig => { - // read options from config - const clientId = envConfig.getString('clientId'); - const clientSecret = envConfig.getString('clientSecret'); - - // Use provided auth handler, or create a default one - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ fullProfile, params }) => ({ - profile: makeProfileInfo(fullProfile, params.id_token), - }); - - // instantiate our OAuthHandlers implementation - const provider = new OktaAuthProvider({ - audience, - clientId, - clientSecret, - callbackUrl, - authHandler, - signInResolver: options?.signIn?.resolver, - resolverContext, + async init({ providers }) { + providers.registerProvider({ + providerId: 'foobar', + factory: createOAuthProviderFactory({ + authenticator: providerAuthenticator, + signInResolverFactories: { + ...commonSignInResolvers, + }, + }), }); - - // Wrap the OAuthHandlers with OAuthProvider, which implements AuthProviderRouteHandlers - return OAuthProvider.fromConfig(globalConfig, provider, { - providerId, - tokenIssuer, - }); - }); - }, - resolvers: { - /** - * Looks up the user by matching their email local part to the entity name. - */ - emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver, - - // ... additional predefined resolvers + }, + }); }, }); ``` -The purpose of the different environments is to allow for a single auth-backend -to serve as the authentication service for multiple different frontend -environments, such as local development, staging, and production. +Now let's implement the actual authenticator for our provider using `Strategy` from a passport package. +The authenticator is responsible for creating the passport strategy and handling the authentication flow using secrets from the config file. + +```ts title="plugins/auth-backend-foobar-provider/src/authenticator.ts" +import { Strategy as ProviderStrategy } from 'passport-provider-a'; +import { + createOAuthAuthenticator, + PassportOAuthAuthenticatorHelper, + PassportOAuthDoneCallback, + PassportProfile, +} from '@backstage/plugin-auth-node'; + +/** @public */ +export const providerAuthenticator = createOAuthAuthenticator({ + defaultProfileTransform: + PassportOAuthAuthenticatorHelper.defaultProfileTransform, + scopes: { + // Scopes required by the provider + required: ['openid', 'email', 'profile', 'offline_access'], + }, + initialize({ callbackUrl, config }) { + const clientId = config.getString('clientId'); + const clientSecret = config.getString('clientSecret'); + + return PassportOAuthAuthenticatorHelper.from( + new ProviderStrategy( + { + clientID: clientId, + clientSecret: clientSecret, + // ... other options + }, + ( + accessToken: string, + refreshToken: string, + params: any, + fullProfile: PassportProfile, + done: PassportOAuthDoneCallback, + ) => { + done( + undefined, + { fullProfile, params, accessToken }, + { refreshToken }, + ); + }, + ), + ); + }, + + async start(input, helper) { + return helper.start(input); + }, + + async authenticate(input, helper) { + return helper.authenticate(input); + }, + + async refresh(input, helper) { + return helper.refresh(input); + }, +}); +``` + +Here are some examples of authenticators that are already implemented in the codebase: + +- [Google](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-google-provider/src/authenticator.ts) +- [GitHub](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-github-provider/src/authenticator.ts) +- [Okta](https://github.com/backstage/backstage/blob/master/plugins/auth-backend-module-okta-provider/src/authenticator.ts) + +### Creating proxy auth based provider + +A proxy auth provider is a provider that uses another provider to authenticate for example Google IAP or AWS ALB, please note that those providers are already supported by Backstage. + +The implementation is similar to the OAuth provider, but the `authenticator` function is different. There are already some examples on how to implement a proxy provider in the codebase, for example [auth-backend-module-gcp-iap-provider](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-gcp-iap-provider) and [auth-backend-module-aws-alb-provider](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-aws-alb-provider) #### Verify Callback @@ -323,27 +268,20 @@ environments, such as local development, staging, and production. > > http://www.passportjs.org/docs/configure/ -**`plugins/auth-backend/src/providers/providerA/index.ts`** is simply -re-exporting the factory function to be used for hooking the provider up to the -backend. +### Add the provider to the backend + +The process for adding the new module is the same as for any other type of module or backend plugin. + +If this provider is internal to your installation the import path that you add to `packages/backend/src/index.ts` would be something like: ```ts -export { createProviderAProvider } from './provider'; +backend.add(import('@internal/plugin-auth-backend-module-foobar-provider')); ``` -### Hook it up to the backend - -**`plugins/auth-backend/src/providers/factories.ts`** When the `auth-backend` -starts it sets up routing for all the available providers by calling -the factory function of each provider. You need to import the factory -function from the provider and add it to the factory: +But if this module is contributed directly to Backstage the module would be imported as ```ts -import { createProviderAProvider } from './providerA'; - -const factories: { [providerId: string]: AuthProviderFactory } = { - providerA: createProviderAProvider, -}; +backend.add(import('@backstage/plugin-auth-backend-module-foobar-provider')); ``` By doing this `auth-backend` automatically adds these endpoints: diff --git a/docs/auth/auth0/provider--old.md b/docs/auth/auth0/provider--old.md new file mode 100644 index 0000000000..a089236aca --- /dev/null +++ b/docs/auth/auth0/provider--old.md @@ -0,0 +1,75 @@ +--- +id: provider--old +title: Auth0 Authentication Provider +sidebar_label: Auth0 +description: Adding Auth0 as an authentication provider in Backstage +--- + +:::info +This documentation is written for the old backend which has been replaced by +[the new backend system](../../backend-system/index.md), being the default since +Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new +backend system, you may want to read [its own article](./provider.md) +instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +The Backstage `core-plugin-api` package comes with an Auth0 authentication +provider that can authenticate users using OAuth. + +## Create an Auth0 Application + +1. Log in to the [Auth0 dashboard](https://manage.auth0.com/dashboard/) +2. Navigate to **Applications** +3. Create an Application + - Name: Backstage (or your custom app name) + - Application type: Single Page Web Application +4. Click on the Settings tab +5. Add under `Application URIs` > `Allowed Callback URLs`: + `http://localhost:7007/api/auth/auth0/handler/frame` +6. Click `Save Changes` + +## Configuration + +The provider configuration can then be added to your `app-config.yaml` under the +root `auth` configuration: + +```yaml +auth: + environment: development + providers: + auth0: + development: + clientId: ${AUTH_AUTH0_CLIENT_ID} + clientSecret: ${AUTH_AUTH0_CLIENT_SECRET} + domain: ${AUTH_AUTH0_DOMAIN_ID} + audience: ${AUTH_AUTH0_AUDIENCE} + connection: ${AUTH_AUTH0_CONNECTION} + connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE} + session: + secret: ${AUTH_SESSION_SECRET} +``` + +The Auth0 provider is a structure with these configuration keys: + +- `clientId`: The Application client ID, found on the Auth0 Application page +- `clientSecret`: The Application client secret, found on the Auth0 Application + page +- `domain`: The Application domain, found on the Auth0 Application page + +It additionally relies on the following configuration to function: + +- `session.secret`: The session secret is a key used for signing and/or encrypting cookies set by the application to maintain session state. In this case, 'your session secret' should be replaced with a long, complex, and unique string that only your application knows. + +Auth0 requires a session, so you need to give the session a secret key. + +## Optional Configuration + +- `audience`: The intended recipients of the token +- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections). +- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter + +## Adding the provider to the Backstage frontend + +To add the provider to the frontend, add the `auth0AuthApi` reference and +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#sign-in-configuration). diff --git a/docs/auth/auth0/provider.md b/docs/auth/auth0/provider.md index ae44114906..bc2a02fe1f 100644 --- a/docs/auth/auth0/provider.md +++ b/docs/auth/auth0/provider.md @@ -5,6 +5,13 @@ sidebar_label: Auth0 description: Adding Auth0 as an authentication provider in Backstage --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage +[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend +system, you may want to read [its own article](./provider--old.md) +instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + The Backstage `core-plugin-api` package comes with an Auth0 authentication provider that can authenticate users using OAuth. @@ -54,12 +61,46 @@ It additionally relies on the following configuration to function: Auth0 requires a session, so you need to give the session a secret key. -## Optional Configuration +### Optional - `audience`: The intended recipients of the token - `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections). - `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. + +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + +## Backend Installation + +To add the provider to the backend we will first need to install the package by running this command: + +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-auth0-provider +``` + +Then we will need to add this line: + +```ts title="packages/backend/src/index.ts" +import { createBackend } from '@backstage/backend-defaults'; +//... +backend.add(import('@backstage/plugin-auth-backend')); +// highlight-add-next-line +backend.add(import('@backstage/plugin-auth-backend-module-auth0-provider')); +//... +``` + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `auth0AuthApi` reference and diff --git a/docs/auth/bitbucketServer/provider--old.md b/docs/auth/bitbucketServer/provider--old.md new file mode 100644 index 0000000000..fdd8fa35be --- /dev/null +++ b/docs/auth/bitbucketServer/provider--old.md @@ -0,0 +1,60 @@ +--- +id: provider--od +title: Bitbucket Server Authentication Provider +sidebar_label: Bitbucket Server +description: Adding Bitbucket Server OAuth as an authentication provider in Backstage +--- + +:::info +This documentation is written for the old backend which has been replaced by +[the new backend system](../../backend-system/index.md), being the default since +Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new +backend system, you may want to read [its own article](./provider.md) +instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate +users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud. + +## Create an Application Link in Bitbucket Server + +To add Bitbucket Server authentication, you must create an incoming application link. Follow the steps described in +the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html) +to create one. + +## Configuration + +The provider configuration can then be added to your `app-config.yaml` under the root `auth` configuration: + +```yaml +auth: + environment: development + providers: + bitbucketServer: + development: + host: bitbucket.org + clientId: ${AUTH_BITBUCKET_SERVER_CLIENT_ID} + clientSecret: ${AUTH_BITBUCKET_SERVER_CLIENT_SECRET} +``` + +The Bitbucket Server provider is a structure with two configuration keys: + +- `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`. +- `clientSecret`: The client secret tied to the generated client ID. + +## Adding the provider to the Backstage frontend + +To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown +in [Adding the provider to the sign-in page](../index.md#sign-in-configuration). + +## Using Bitbucket Server for sign-in + +In order to use the Bitbucket Server provider for sign-in, you must configure it with a `signIn.resolver`. See +the [Sign-In Resolver documentation](../identity-resolver.md) for more details on how this is done. Note that for the +Bitbucket Server provider, you'll want to use `bitbucketServer` as the provider ID, +and `providers.bitbucketServer.create` for the provider factory. + +The `@backstage/plugin-auth-backend` plugin also comes with a built-in resolver that can be used if desired. +The `emailMatchingUserEntityProfileEmail` identifies users by matching their Bitbucket Server email address to the email +address of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will +not be able to sign in with this resolver. diff --git a/docs/auth/bitbucketServer/provider.md b/docs/auth/bitbucketServer/provider.md index 82aebc20b2..b15f0b206a 100644 --- a/docs/auth/bitbucketServer/provider.md +++ b/docs/auth/bitbucketServer/provider.md @@ -5,6 +5,13 @@ sidebar_label: Bitbucket Server description: Adding Bitbucket Server OAuth as an authentication provider in Backstage --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage +[version 1.24](../../releases/v1.24.0.md). If you are still on the old backend +system, you may want to read [its own article](./provider--old.md) +instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud. @@ -34,19 +41,43 @@ The Bitbucket Server provider is a structure with two configuration keys: - `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`. - `clientSecret`: The client secret tied to the generated client ID. +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. + +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + +## Backend Installation + +To add the provider to the backend we will first need to install the package by running this command: + +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-bitbucket-server-provider +``` + +Then we will need to add this line: + +```ts title="packages/backend/src/index.ts" +//... +backend.add(import('@backstage/plugin-auth-backend')); +// highlight-add-start +backend.add( + import('@backstage/plugin-auth-backend-module-bitbucket-server-provider'), +); +// highlight-add-end +//... +``` + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown in [Adding the provider to the sign-in page](../index.md#sign-in-configuration). - -## Using Bitbucket Server for sign-in - -In order to use the Bitbucket Server provider for sign-in, you must configure it with a `signIn.resolver`. See -the [Sign-In Resolver documentation](../identity-resolver.md) for more details on how this is done. Note that for the -Bitbucket Server provider, you'll want to use `bitbucketServer` as the provider ID, -and `providers.bitbucketServer.create` for the provider factory. - -The `@backstage/plugin-auth-backend` plugin also comes with a built-in resolver that can be used if desired. -The `emailMatchingUserEntityProfileEmail` identifies users by matching their Bitbucket Server email address to the email -address of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will -not be able to sign in with this resolver. diff --git a/docs/auth/index.md b/docs/auth/index.md index de579d039a..2384943c52 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -36,8 +36,7 @@ Backstage comes with many common authentication providers in the core library: - [OneLogin](onelogin/provider.md) - [VMware Cloud](vmware-cloud/provider.md) -These built-in providers handle the authentication flow for a particular service -including required scopes, callbacks, etc. These providers are each added to a +These built-in providers handle the authentication flow for a particular service, including required scopes, callbacks, etc. These providers are each added to a Backstage app in a similar way. ## Configuring Authentication Providers @@ -58,7 +57,7 @@ auth: See the documentation for a particular provider to see what configuration is needed. -The `providers` key may have several authentication providers, if multiple +The `providers` key may have several authentication providers if multiple authentication methods are supported. Each provider may also have configuration for different authentication environments (development, production, etc). This allows a single auth backend to serve multiple environments, such as running a @@ -68,15 +67,14 @@ the local `auth.environment` setting will be selected. ## Sign-In Configuration Using an authentication provider for sign-in is something you need to configure -both in the frontend app, as well as the `auth` backend plugin. For information +both in the frontend app as well as the `auth` backend plugin. For information on how to configure the backend app, see [Sign-in Identities and Resolvers](./identity-resolver.md). The rest of this section will focus on how to configure sign-in for the frontend app. Sign-in is configured by providing a custom `SignInPage` app component. It will be rendered before any other routes in the app and is responsible for providing the identity of the current user. The `SignInPage` can render any number of pages and -components, or just blank space with logic running in the background. In the end -however it must provide a valid Backstage user identity through the `onSignInSuccess` +components, or just blank space with logic running in the background. In the end, however, it must provide a valid Backstage user identity through the `onSignInSuccess` callback prop, at which point the rest of the app is rendered. If you want to, you can use the `SignInPage` component that is provided by `@backstage/core-components`, @@ -149,7 +147,7 @@ const app = createApp({ ### Conditionally Render Sign In Provider -In the above example you have both Guest and GitHub sign-in options, this is helpful for non-production but in Production you will most likely not want to offer Guest access. You can easily use information from your config to help conditionally render the provider: +In the above example, you have both Guest and GitHub sign-in options; this is helpful for non-production, but in Production you will most likely not want to offer Guest access. You can easily use information from your config to help conditionally render the provider: ```tsx title="packages/app/src/App.tsx" import { @@ -207,7 +205,7 @@ and [OAuth2 Proxy](./oauth2-proxy/provider.md). When using a proxy provider, you'll end up wanting to use a different sign-in page, as there is no need for further user interaction once you've signed in towards the proxy. -All the sign-in page needs to do is to call the `/refresh` endpoint of the auth providers +All the sign-in page needs to do is call the `/refresh` endpoint of the auth providers to get the existing session, which is exactly what the `ProxiedSignInPage` does. The only thing you need to do to configure the `ProxiedSignInPage` is to pass the ID of the provider like this: @@ -250,7 +248,7 @@ Headers can also be returned in an async manner: A downside of this method is that it can be cumbersome to set up for local development. As a workaround for this, it's possible to dynamically select the sign-in page based on -what environment the app is running in, and then use a different sign-in method for local +what environment the app is running in and then use a different sign-in method for local development, if one is needed at all. Depending on the exact setup, one might choose to select the sign-in method based on the `process.env.NODE_ENV` environment variable, by checking the `hostname` of the current location, or by accessing the configuration API @@ -286,7 +284,7 @@ sign-in resolvers so that they resolve to the same identity regardless of the me ## Scaffolder Configuration (Software Templates) -If you want to use the authentication capabilities of the [Repository Picker](../features/software-templates/writing-templates.md#the-repository-picker) inside your software templates you will need to configure the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi) alongside your authentication provider. It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed. +If you want to use the authentication capabilities of the [Repository Picker](../features/software-templates/writing-templates.md#the-repository-picker) inside your software templates, you will need to configure the [`ScmAuthApi`](https://backstage.io/docs/reference/integration-react.scmauthapi) alongside your authentication provider. It is an API used to authenticate towards different SCM systems in a generic way, based on what resource is being accessed. To set it up, you'll need to add an API factory entry to `packages/app/src/apis.ts`. The example below sets up the `ScmAuthApi` for an already configured GitLab authentication provider: @@ -305,7 +303,7 @@ In case you are using a custom authentication providers, you might need to add a ## For Plugin Developers The Backstage frontend core APIs provide a set of Utility APIs for plugin developers -to use, both to access the user identity, as well as third party resources. +to use, both to access the user identity as well as third-party resources. ### Identity for Plugin Developers @@ -323,21 +321,21 @@ to interact directly with the `IdentityApi`. ### Accessing Third Party Resources -A common pattern for talking to third party services in Backstage is +A common pattern for talking to third-party services in Backstage is user-to-server requests, where short-lived OAuth Access Tokens are requested by plugins to authenticate calls to external services. These calls can be made either directly to the services or through a backend plugin or service. -By relying on user-to-server calls we keep the coupling between the frontend and -backend low, and provide a much lower barrier for plugins to make use of third -party services. This is in comparison to for example a session-based system, +By relying on user-to-server calls, we keep the coupling between the frontend and +backend low and provide a much lower barrier for plugins to make use of third +party services. This is in comparison to, for example, a session-based system where access tokens are stored server-side. Such a solution would require a much deeper coupling between the auth backend plugin, its session storage, and other backend plugins or separate services. A goal of Backstage is to make it as easy as possible to create new plugins, and an auth solution based on user-to-server OAuth helps in that regard. -The method with which frontend plugins request access to third party services is +The method with which frontend plugins request access to third-party services is through [Utility APIs](../api/utility-apis.md) for each service provider. These are all suffixed with `*AuthApiRef`, for example `githubAuthApiRef`. For a full list of providers, see the diff --git a/docs/auth/oauth2-proxy/provider.md b/docs/auth/oauth2-proxy/provider.md index 0901685b2f..84e2b307e6 100644 --- a/docs/auth/oauth2-proxy/provider.md +++ b/docs/auth/oauth2-proxy/provider.md @@ -13,6 +13,12 @@ a cluster. In general the `oauth2-proxy` supports all OpenID Connect providers, for more details check this [list of supported providers](https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider). +:::note Note + +OAuth2 Proxy does not provide a way to authenticate requests, you must instead ensure that your Backstage instance is only accessible through the OAuth2 Proxy. If you need more strict validation, consider using a different provider. + +::: + ## Configuration The provider configuration can be added to your `app-config.yaml` under the root diff --git a/docs/auth/oidc--old.md b/docs/auth/oidc--old.md new file mode 100644 index 0000000000..904e48c9a3 --- /dev/null +++ b/docs/auth/oidc--old.md @@ -0,0 +1,289 @@ +--- +id: oidc--old +title: OIDC provider from scratch +description: This section shows how to use an OIDC provider from scratch, same steps apply for custom providers. +--- + +:::info +This documentation is written for the old backend which has been replaced by +[the new backend system](../backend-system/index.md), being the default since +Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new +backend system, you may want to read [its own article](./oidc.md) +instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +This section shows how to use an OIDC provider from scratch, same steps apply for custom +providers. Please note these steps are for using a provider, not how to implement one, +and Backstage recommends creating custom providers specific to the IDP, so we'll use a +`azureOIDC` provider throughout this example, feel free to change any of those refs +to your provider name. + +## Summary + +To add providers not enabled by default like OIDC, we need to follow some steps, we +assume you already have a sign in page to which we'll add the provider so users can +sign in through the provider. In simple steps here's how you enable the provider: + +- Create an API reference to identify the provider. +- Create the API factory that will handle the authentication. +- Add or reuse an auth provider so you can authenticate. +- Add or reuse a resolver to handle the result from the authentication. +- Configure the provider to access your 3rd party auth solution. +- Add the provider to sign in page so users can login with it. + +We'll explain each step more in detail next. + +### The API reference + +An API reference exist for the sake of **Dependency Injection**, check [Utility APIs][4] +for extended explanation. + +In this OIDC example, we'll create the API reference directly in the +`packages/app/src/apis.ts` file, it is not a requirement to put the reference in this +file. Any location will do as long as it's available to be imported to where the API +factory is, as well as easily accessible to the rest of the application so any package +and plugin can inject the API instance when necessary. + +An example of such would be when you use an auth provider from a library installed with +NPM, or any other library repository, you would import the API ref from the library. + +```ts +export const azureOIDCAuthApiRef: ApiRef< + OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'auth.my-custom-provider', +}); +``` + +Please note a few things, the ID can be anything you want as long as it doesn't conflict +with other refs, backstage recommends to use a custom name that references your custom +provider, for example we are using OIDC protocol with Azure, so we could use something +like `auth.azure.oidc` as well. + +Also we're exporting this reference, as well as the `typings`, we need to +be able to import this reference anywhere in the app, and the `typings` will tell typescript +what instance we're getting from DI when injecting the API. In this case we are defining +an API for authentication, so we tell TS that this instance complies with 4 API +interfaces: + +- The OICD API that will handle authentication. +- Profile API for requesting user profile info from the auth provider in question. +- Backstage identity API to handle and associate the user profile with backstage identity. +- Session API, to handle the session the user will have while logged in. + +### The API Factory + +A factory is a function that can take some parameters or dependencies and return an +instance of something, in our case it will be a function that requests some backstage +APIs and use them to create an instance of an OIDC API provider. + +Please note that this function only runs (creates the instance) when somewhere else in +the app you request the DI to give you an instance of the OIDC provider using the API ref +defined above, and the DI will only run this function the first time, from then on any +other DI injection will just receive the same instance created the first time, basically +the instance is cached by the DI library, a singleton. + +Let's add our OIDC API factory to the APIs array in the `packages/app/src/apis.ts` file: + +```ts title="packages/app/src/apis.ts" +/* highlight-add-next-line */ +import { OAuth2 } from '@backstage/core-app-api'; + +export const apis: AnyApiFactory[] = [ + /* highlight-add-start */ + createApiFactory({ + api: azureOIDCAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OAuth2.create({ + configApi, + discoveryApi, + oauthRequestApi, + provider: { + id: 'my-auth-provider', + title: 'My custom auth provider', + icon: () => null, + }, + environment: configApi.getOptionalString('auth.environment'), + defaultScopes: ['openid', 'profile', 'email'], + popupOptions: { + // optional, used to customize login in popup size + size: { + fullscreen: true, + }, + /** + * or specify popup width and height + * size: { + width: 1000, + height: 1000, + } + */ + }, + }), + }), + /* highlight-add-end */ + // .. +]; +``` + +Please note we're importing the `OAuth2` class from `@backstage/core-app-api` effectively +delegating the authentication to it. Also we're using the `my-auth-provider` ID to tell +`OAuth2` to use the auth provider we'll define in the next section, and added the default +scopes to request ID, profile, email and user read permissions. + +## The Auth Provider + +The Auth Provider is responsible for authenticating with the 3rd party service, and give +us back the credentials, here's where you pick which protocol to use, be it Auth0, OAuth2, +OIDC, SAML or any other that your 3rd party IDP provider supports. + +For this example we'll use OIDC, we pass a factory to the `providerFactories` object with +the ID you picked to represent the Auth provider, this ID has to match with the provider's +`id` inside the API factory, the yaml config provider key under `auth.providers`, and the +callback URI provider segment (you'll have to configure your IDP to handle the callback +URI properly). + +```ts +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + discovery: env.discovery, + tokenManager: env.tokenManager, + providerFactories: { + ...defaultAuthProviderFactories, + /* highlight-add-next-line */ + 'my-auth-provider': providers.oidc.create({}), + }, + // .. +}) +``` + +### The Resolver + +Resolvers exist to map user identity from the 3rd party (in this case an azure IDP +provider) to the backstage user identity, for a detailed explanation check the +[Identity Resolver][1] page, it explains how to write a custom resolver as well as +linking the built in resolvers of backstage. + +The default OIDC provider does not support SignIn, we need to add such support by +adding a resolver for a SignIn request. + +The OIDC provider doesn't provide any build-in resolvers, so we'll need to define our own: + +```ts +import { + DEFAULT_NAMESPACE, + /* highlight-add-next-line */ + stringifyEntityRef, +} from '@backstage/catalog-model'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + discovery: env.discovery, + tokenManager: env.tokenManager, + providerFactories: { + ...defaultAuthProviderFactories, + 'my-auth-provider': providers.oidc.create({ + /* highlight-add-start */ + signIn: { + resolver(info, ctx) { + const userRef = stringifyEntityRef({ + kind: 'User', + name: info.result.userinfo.sub, + namespace: DEFAULT_NAMESPACE, + }); + return ctx.issueToken({ + claims: { + sub: userRef, // The user's own identity + ent: [userRef], // A list of identities that the user claims ownership through + }, + }); + }, + }, + /* highlight-add-end */ + }), + }, + // .. + }) +``` + +### The configuration + +Since we are using our custom OIDC Auth Provider, we need to add a configuration based +on the provider used, in this case based on OIDC protocol (remember the 3rd party has to +support the protocol). + +In this example we'll configure OIDC with `my-auth-provider`, to do so we need to +[Create app registration][2] in the Azure console, the only difference is that the +`http://localhost:7007/api/auth/microsoft/handler/frame` URL needs to change to +`http://localhost:7007/api/auth/my-auth-provider/handler/frame`. + +Then we need to configure the env variables for the provider, based on the provider's code +in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following variables +in the `app-config.yaml`: + +```yaml title="app-config.yaml" +auth: + environment: development + ### Providing an auth.session.secret will enable session support in the auth-backend + session: + secret: ${SESSION_SECRET} + providers: + my-auth-provider: + development: + metadataUrl: https://example.com/.well-known/openid-configuration + clientId: ${AUTH_MY_CLIENT_ID} + clientSecret: ${AUTH_MY_CLIENT_SECRET} +``` + +Anything enclosed in `${}` can be replaced directly in the yaml, or provided as +environment variables, the way you obtain all these except `scope` and `prompt` is to +check the App Registration you created: + +- `clientId`: Grab from the Overview page. +- `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a + new secret. +- `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL. +- `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will + hold these 2 urls somewhere in there. +- `tokenEndpointAuthMethod`: Don't define it, use the default unless you know what it does. +- `tokenSignedResponseAlg`: Don't define it, use the default unless you know what it does. +- `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory, + basically the same thing. +- `prompt`: Recommended to use `auto` so the browser will request login to the IDP if the + user has no active session. + +Note that for the time being, any change in this yaml file requires a restart of the app, +also you need to have the `session.secret` part to use OIDC (some other providers might +need this as well) to support user sessions. + +### The Sign In provider + +The last step is to add the provider to the `SignInPage` so users can sign in with your +new provider, please follow the [Sign In Configuration][3] docs, here's where you import +and use the API reference we defined earlier. + +## Note + +These steps apply to most if not all the providers, including custom providers, the main +difference between different providers will be the contents of the API factory, the code +in the Auth Provider Factory, the resolver, and the different variables each provider +needs in the YAML config or env variables. + +[1]: https://backstage.io/docs/auth/identity-resolver +[2]: https://backstage.io/docs/auth/microsoft/provider#create-an-app-registration-on-azure +[3]: https://backstage.io/docs/auth/#sign-in-configuration +[4]: https://backstage.io/docs/api/utility-apis diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 98d0e63255..4040a272b4 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -4,6 +4,13 @@ title: OIDC provider from scratch description: This section shows how to use an OIDC provider from scratch, same steps apply for custom providers. --- +:::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](./oidc--old.md) +instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + This section shows how to use an OIDC provider from scratch, same steps apply for custom providers. Please note these steps are for using a provider, not how to implement one, and Backstage recommends creating custom providers specific to the IDP, so we'll use a @@ -13,7 +20,7 @@ to your provider name. ## Summary To add providers not enabled by default like OIDC, we need to follow some steps, we -assume you already have a sign in page to which we'll add the provider so users can +assume you already have a sign-in page to which we'll add the provider so users can sign in through the provider. In simple steps here's how you enable the provider: - Create an API reference to identify the provider. @@ -133,85 +140,86 @@ The Auth Provider is responsible for authenticating with the 3rd party service, us back the credentials, here's where you pick which protocol to use, be it Auth0, OAuth2, OIDC, SAML or any other that your 3rd party IDP provider supports. -For this example we'll use OIDC, we pass a factory to the `providerFactories` object with -the ID you picked to represent the Auth provider, this ID has to match with the provider's -`id` inside the API factory, the yaml config provider key under `auth.providers`, and the -callback URI provider segment (you'll have to configure your IDP to handle the callback -URI properly). - -```ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - ...defaultAuthProviderFactories, - /* highlight-add-next-line */ - 'my-auth-provider': providers.oidc.create({}), - }, - // .. -}) -``` - ### The Resolver Resolvers exist to map user identity from the 3rd party (in this case an azure IDP -provider) to the backstage user identity, for a detailed explanation check the -[Identity Resolver][1] page, it explains how to write a custom resolver as well as -linking the built in resolvers of backstage. +provider) to the backstage user identity. -The default OIDC provider does not support SignIn, we need to add such support by -adding a resolver for a SignIn request. +The default OIDC provider has built-in resolvers, here is how you configure them: -The OIDC provider doesn't provide any build-in resolvers, so we'll need to define our own: - -```ts -import { - DEFAULT_NAMESPACE, - /* highlight-add-next-line */ - stringifyEntityRef, -} from '@backstage/catalog-model'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - ...defaultAuthProviderFactories, - 'my-auth-provider': providers.oidc.create({ - /* highlight-add-start */ - signIn: { - resolver(info, ctx) { - const userRef = stringifyEntityRef({ - kind: 'User', - name: info.result.userinfo.sub, - namespace: DEFAULT_NAMESPACE, - }); - return ctx.issueToken({ - claims: { - sub: userRef, // The user's own identity - ent: [userRef], // A list of identities that the user claims ownership through - }, - }); - }, - }, - /* highlight-add-end */ - }), - }, - // .. - }) +```yaml title="app-config.yaml" +auth: + environment: development + providers: + oidc: + development: + # ... + signIn: + resolvers: + # typically you would pick one of these + - resolver: emailLocalPartMatchingUserEntityName + - resolver: emailMatchingUserEntityProfileEmail ``` +But you can also write a custom resolver as well, see an example below: + +```ts title="in packages/backend/src/index.ts" +/* highlight-add-start */ +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider'; + +const myAuthProviderModule = createBackendModule({ + // This ID must be exactly "auth" because that's the plugin it targets + pluginId: 'auth', + // This ID must be unique, but can be anything + moduleId: 'my-auth-provider', + register(reg) { + reg.registerInit({ + deps: { providers: authProvidersExtensionPoint }, + async init({ providers }) { + providers.registerProvider({ + // This ID must match the actual provider config, e.g. addressing + // auth.providers.azure means that this must be "azure". + providerId: 'my-auth-provider', + // Use createProxyAuthProviderFactory instead if it's one of the proxy + // based providers rather than an OAuth based one + factory: createOAuthProviderFactory({ + // For more info about authenticators please see https://backstage.io/docs/auth/add-auth-provider/#adding-an-oauth-based-provider + authenticator: oidcAuthenticator, + async signInResolver(info, ctx) { + const userRef = stringifyEntityRef({ + kind: 'User', + name: info.result.userinfo.sub, + namespace: DEFAULT_NAMESPACE, + }); + return ctx.issueToken({ + claims: { + sub: userRef, // The user's own identity + ent: [userRef], // A list of identities that the user claims ownership through + }, + }); + }, + }), + }); + }, + }); + }, +}); +/* highlight-add-end */ +//... +backend.add(import('@backstage/plugin-auth-backend')); +/* highlight-add-next-line */ +backend.add(myAuthProviderModule); +//... +``` + +For a more a detailed explanation about resolvers check the +[Identity Resolver][1] page. + ### The configuration Since we are using our custom OIDC Auth Provider, we need to add a configuration based diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index 57f6b20b73..ad437e471e 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -197,7 +197,7 @@ When declaring a service factory you may also want to make the export the buildi ```ts export class DefaultFooService { static create(options: { transform: (foo: string) => string }) { - return new DefaultFooService(options.transform ?? (foo) => foo); + return new DefaultFooService(options.transform ?? ((foo) => foo); } private constructor(private readonly transform: (foo: string) => string) {} diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index 67104c7ba9..fb2923f4c9 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -431,7 +431,7 @@ catalog: /* highlight-add-end */ ``` -#### Github +#### GitHub For `GithubDiscoveryProcessor`, `GithubMultiOrgReaderProcessor` and `GithubOrgReaderProcessor`, first migrate to the equivalent Entity Provider. @@ -445,7 +445,7 @@ backend.add(import('@backstage/plugin-catalog-backend-module-github/alpha')); ``` If you were providing a `schedule` in code, this now needs to be set via configuration. -All other Github configuration in `app-config.yaml` remains the same. +All other GitHub configuration in `app-config.yaml` remains the same. ```yaml title="app-config.yaml" catalog: diff --git a/docs/backend-system/core-services/auth.md b/docs/backend-system/core-services/auth.md index f1243f11fe..8a3a63805d 100644 --- a/docs/backend-system/core-services/auth.md +++ b/docs/backend-system/core-services/auth.md @@ -73,7 +73,8 @@ router.get('/makes-calls', async (req, res) => { onBehalfOf: await httpAuth.credentials(req), targetPluginId: 'catalog', }); - // make a call using the token +}); +// make a call using the token ``` This ensures that the original caller and their associated permissions are @@ -106,7 +107,7 @@ objects. For example checking what type of principal (caller type - e.g. user or service) they represent. For example: ```ts -if (auth.isPrincipal(credentials, 'user)) { +if (auth.isPrincipal(credentials, 'user')) { // In here, the TypeScript type of the credentials object has been properly // narrowed to `BackstageCredentials` so you can // access its specific properties such as `credentials.principal.userEntityRef`. diff --git a/docs/backend-system/core-services/http-auth.md b/docs/backend-system/core-services/http-auth.md index 05a26fc075..13938c921d 100644 --- a/docs/backend-system/core-services/http-auth.md +++ b/docs/backend-system/core-services/http-auth.md @@ -47,6 +47,7 @@ can do so like this: router.get('/some-request', async (req, res) => { const credentials = await httpAuth.credentials(req, { allow: ['user'] }); // Do something with the credentials here +}); ``` The second argument is optional, but in this example we specified that we only diff --git a/docs/backend-system/core-services/user-info.md b/docs/backend-system/core-services/user-info.md index e9d17f1dd7..73f461d1ec 100644 --- a/docs/backend-system/core-services/user-info.md +++ b/docs/backend-system/core-services/user-info.md @@ -47,6 +47,7 @@ additional information about that principal. router.get('/some-request', async (req, res) => { const credentials = await httpAuth.credentials(req, { allow: ['user'] }); const info = await userInfo.getUserInfo(credentials); +}); ``` The `userInfo` service only deals with credentials that contain user principals, @@ -61,6 +62,8 @@ router.get('/some-request', async (req, res) => { if (auth.isPrincipal(credentials, 'user')) { const info = await userInfo.getUserInfo(credentials); // ... + } +}); ``` The user info contains data that was extracted during sign-in for the given diff --git a/docs/deployment/flightcontrol.md b/docs/deployment/flightcontrol.md index 14e0b308a6..fb2af87dd2 100644 --- a/docs/deployment/flightcontrol.md +++ b/docs/deployment/flightcontrol.md @@ -7,7 +7,7 @@ description: Deploying Backstage to AWS Fargate via Flightcontrol This guide explains how to deploy Backstage to [Flightcontrol](https://www.flightcontrol.dev?ref=backstage), a platform that fully automates deployments to Amazon Web Services (AWS). Flightcontrol supports git-driven and image registry deployments. -Before you begin, make sure you have a [Flightcontrol account](https://app.flightcontrol.dev/signup?ref=backstage) and a [Github account](https://github.com/login) to follow this guide. +Before you begin, make sure you have a [Flightcontrol account](https://app.flightcontrol.dev/signup?ref=backstage) and a [GitHub account](https://github.com/login) to follow this guide. # Creating a Dockerfile and .dockerignore diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md index 279c9cbf3f..bd6a15a542 100644 --- a/docs/features/kubernetes/installation.md +++ b/docs/features/kubernetes/installation.md @@ -96,6 +96,7 @@ async function main() { // ... /* highlight-add-next-line */ apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv)); +} ``` That's it! The Kubernetes frontend and backend have now been added to your diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index 67382aa6ce..dff09a9397 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -31,7 +31,7 @@ catalog (`UrlReaderProcessor`), so no processor configuration is needed. This processor _does however_ need an [integration](../../integrations/index.md) to understand how to retrieve a given URL. For the example above, you would need to configure the [GitHub integration](../../integrations/github/locations.md) to -read files from github.com. +read files from [github.com](https://github.com/). The locations added through static configuration cannot be removed through the catalog locations API. To remove these locations, you must remove them from the diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index 51acd44c1d..f9d20d03c2 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -597,7 +597,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; /* highlight-add-next-line */ import { FoobarEntitiesProcessor } from './providers'; diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index 98fae5a95e..5d482fc3e4 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -543,6 +543,7 @@ export class UserEntityProvider implements EntityProvider { locationKey: 'hr-user-https://www.hrurl.com/', })), }) + } } ``` @@ -1031,3 +1032,292 @@ backend.add(catalogModuleCustomDataParser); backend.start(); ``` + +## Incremental Entity Provider + +For large data sources that may not fit into memory but support pagination, the Incremental Entity Provider offers an efficient way to ingest data incrementally, handling deletions and updates seamlessly while minimizing memory usage. + +You can find more details about [why it was created](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#why-did-we-create-it) and its [requirements](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#requirements). + +### Installation + +1. Install `@backstage/plugin-catalog-backend-module-incremental-ingestion` with `yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-incremental-ingestion` from the Backstage root directory. + +2. Add the following code to the `packages/backend/src/index.ts` file: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +/* highlight-add-start */ +backend.add( + import( + '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha' + ), +); +/* highlight-add-end */ + +backend.start(); +``` + +### Writing an Incremental Entity Provider + +To create an Incremental Entity Provider, you need to know how to retrieve a single page of data from an API with pagination. The `IncrementalEntityProvider` facilitates this by requiring: + +- **getProviderName:** A unique name to avoid conflicts with other providers. +- **next:** Fetches a specific page of entities, moving the cursor forward. +- **around:** Handles setup and tear-down, wrapping the process that iterates through multiple pages. + +For more information on compatibility, refer to the [requirements](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion#requirements). + +In this tutorial, we'll implement an Incremental Entity Provider that interacts with an imaginary API to fetch a list of imaginary services. + +```ts +interface MyApiClient { + getServices(page: number): MyPaginatedResults; +} + +interface MyPaginatedResults { + items: T[]; + totalPages: number; +} + +interface Service { + name: string; +} +``` + +These are the only 3 methods that you need to implement. `getProviderName()` is pretty self explanatory and it's identical to the `getProviderName()` method on a regular Entity Provider. + +```ts +import { IncrementalEntityProvider } from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; + +// This will include your pagination information, let's say our API accepts a `page` parameter. +// In this case, the cursor will include `page` +interface Cursor { + page: number; +} + +// This interface describes the type of data that will be passed to your burst function. +interface Context { + apiClient: MyApiClient; +} + +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + getProviderName() { + return `MyIncrementalEntityProvider`; + } +} +``` + +`around` method is used for setup and tear-down. For example, if you need to create a client that will connect to the API, you would do that here. + +```ts +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + getProviderName() { + return `MyIncrementalEntityProvider`; + } + + async around(burst: (context: Context) => Promise): Promise { + const apiClient = new MyApiClient(); + + await burst({ apiClient }); + + // If you need to do any teardown, you can do it here. + } +} +``` + +If you need to pass a token to your API, then you can create a constructor that will receive a token and use the token to setup the client. + +```ts +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + private readonly token: string; + + constructor(token: string) { + this.token = token; + } + + getProviderName() { + return `MyIncrementalEntityProvider`; + } + + async around(burst: (context: Context) => Promise): Promise { + const apiClient = new MyApiClient(this.token); + + await burst({ apiClient }); + } +} +``` + +The last step is to implement the actual `next` method that will accept the cursor, call the API, process the result and return the result. + +```ts +export class MyIncrementalEntityProvider + implements IncrementalEntityProvider +{ + private readonly token: string; + + constructor(token: string) { + this.token = token; + } + + getProviderName() { + return `MyIncrementalEntityProvider`; + } + + async around(burst: (context: Context) => Promise): Promise { + const apiClient = new MyApiClient(this.token); + + await burst({ apiClient }); + } + + async next( + context: Context, + cursor?: Cursor = { page: 1 }, + ): Promise> { + const { apiClient } = context; + + // call your API with the current cursor + const data = await apiClient.getServices(cursor); + + // calculate the next page + const nextPage = page + 1; + + // figure out if there are any more pages to fetch + const done = nextPage > data.totalPages; + + // convert returned items into entities + const entities = data.items.map(item => ({ + entity: { + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + metadata: { + name: item.name, + annotations: { + // You need to define these, otherwise they'll fail validation + [ANNOTATION_LOCATION]: this.getProviderName(), + [ANNOTATION_ORIGIN_LOCATION]: this.getProviderName(), + }, + }, + spec: { + type: 'service', + lifecycle: 'production', // Ideally your source has this information + owner: 'unknown', // Ideally your source has this information + }, + }, + })); + + // create the next cursor + const nextCursor = { + page: nextPage, + }; + + return { + done, + entities, + cursor: nextCursor, + }; + } +} +``` + +Now that you have your new Incremental Entity Provider, we can connect it to the catalog. + +### Installing the Incremental Entity Provider + +We'll assume you followed the [Installation](#installation) instructions. Now create a module inside `packages/backend/src/extensions/catalogCustomIncrementalIngestion.ts`. + +```ts title="packages/backend/src/extensions/catalogCustomIncrementalIngestion.ts" +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { incrementalIngestionProvidersExtensionPoint } from '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha'; + +export const catalogModuleCustomIncrementalIngestionProvider = + createBackendModule({ + pluginId: 'catalog', + moduleId: 'custom-incremental-ingestion-provider', + register(env) { + env.registerInit({ + deps: { + incrementalBuilder: incrementalIngestionProvidersExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ incrementalBuilder, config }) { + // Assuming the token for the API comes from config + const token = config.getString('myApiClient.token'); + const myEntityProvider = new MyIncrementalEntityProvider(token); + + const options = { + // How long should it attempt to read pages from the API in a + // single burst? Keep this short. The Incremental Entity Provider + // will attempt to read as many pages as it can in this time + burstLength: { seconds: 3 }, + + // How long should it wait between bursts? + burstInterval: { seconds: 3 }, + + // How long should it rest before re-ingesting again? + restLength: { day: 1 }, + + // Optional back-off configuration - how long should it wait to retry + // in the event of an error? + backoff: [ + { seconds: 5 }, + { seconds: 30 }, + { minutes: 10 }, + { hours: 3 }, + ], + + // Optional. Use this to prevent removal of entities above a given + // percentage. This can be helpful if a data source is flaky and + // sometimes returns a successful status, but fewer than expected + // assets to add or maintain in the catalog. + rejectRemovalsAbovePercentage: 5, + + // Optional. Similar to rejectRemovalsAbovePercentage, except it + // applies to complete, 100% failure of a data source. If true, + // a data source that returns a successful status but does not + // provide any assets to turn into entities will have its empty + // data set rejected. + rejectEmptySourceCollections: true, + }; + + incrementalBuilder.addProvider({ + provider: myEntityProvider, + options, + }); + }, + }); + }, + }); +``` + +Add the module to `packages/backend/src/index.ts` + +```ts title="packages/backend/src/index.ts" +/* highlight-add-next-line */ +import { catalogModuleCustomIncrementalIngestionProvider } from './extensions/catalogCustomIncrementalIngestion'; + +const backend = createBackend(); + +backend.add( + import( + '@backstage/plugin-catalog-backend-module-incremental-ingestion/alpha' + ), +); + +/* highlight-add-next-line */ +backend.add(catalogModuleCustomIncrementalIngestionProvider); + +backend.start(); +``` + +For a deep dive into the technical details of the Incremental Entity Provider, see [the README](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-incremental-ingestion). diff --git a/docs/features/software-catalog/faq.md b/docs/features/software-catalog/faq.md new file mode 100644 index 0000000000..d44414c1a7 --- /dev/null +++ b/docs/features/software-catalog/faq.md @@ -0,0 +1,26 @@ +--- +id: faq +title: Catalog FAQ +sidebar_label: FAQ +description: This page answers frequently asked questions about the catalog +--- + +This page answers frequently asked questions about the catalog. + +## Is it all that important to have users and groups in the catalog at all? + +Yes. One of the most important concepts in the catalog is that it exposes your org structure and ownership properly, allowing your users to effectively understand and communicate around your systems. Having catalog entries for users and groups enables end users to navigate around Backstage and click on those owners and being presented with rich information pages around them, instead of getting 404 Not Found pages. + +## Can I create users in the catalog on-demand as they sign in? + +When standing up a new Backstage instance, adopters are faced with the realization that the catalog tends to have interactions with sign-in. Therefore the question often comes up, whether it's doable to have users pop up in the catalog on-demand only as they sign in. + +This should really be avoided. Our general guidance is to set up a proper integration upfront with your authority for organizational data ([LDAP](../../integrations/ldap/org.md), [Azure](../../integrations/azure/org.md), bespoke HR systems, etc) and batch ingest _all_ users and groups from there into the catalog, whether they sign in or not. This tends to give the superior experience for users, with the smallest amount possible of complexity and frustration. + +To give some background, [signing in](../../auth/index.md) technically only requires the `auth` backend which supports the flows that establish who the current user is. At the end of that process, a so called [sign-in resolver](../../auth/identity-resolver.md) is tasked with translating the third party established identity (for example the attributes returned for your AD entry) into a Backstage identity. This important step is made much simpler if the catalog is populated with users and groups from that same third party, because identities line up trivially and you can use the out-of-the-box provided sign-in resolvers for it. As a side note, you can also write your own resolver that does not interact with the catalog at all if you so desire, but let's assume that you do not chose this advanced option. + +Doing on-demand user creation _is_ technically possible by writing custom [entity providers](./external-integrations.md). But it comes with significant problems both for technical and end user quality of life reasons. + +On the technical side, this is unwanted complexity. You need to implement and maintain a custom provider, instead of what usually amounts to a very easily set-up batch ingestion schedule with providers that come out of the box. Also even if you do this, the catalog is an eventually consistent engine. The user that the provider feeds into the system is not guaranteed to appear immediately. Your experience will likely be only partially functional at bootstrapping time which may have unwanted side effects. + +On the user experience side, a Backstage experience without complete organizational data is a serious hindrance to getting the full power out of the tool. Your users won't be able to click on owners and seeing who they are and what teams they belong to. They won't be able to find out what the communications paths are when they need to reach you or your managers when something goes wrong or they have a feature request. They can't get an overview of what teams own and how they relate to each other. It will be a much more barren experience. Organizational data is highly valuable to have centrally available, complete and correct. diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index 301fb4c457..5bfc038c1d 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -45,13 +45,8 @@ backend.add( // scaffolder plugin backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); -{ - /* highlight-add-start */ -} +/* highlight-add-next-line */ backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); -{ - /* highlight-add-end */ -} backend.start(); ``` @@ -87,16 +82,16 @@ allow most templates built for `fetch:cookiecutter` to work without any changes. ```yaml title="template.yaml" steps: - id: fetch-base - name: Fetch Base - # highlight-remove-next-line - action: fetch:cookiecutter - # highlight-add-next-line - action: fetch:template - input: - url: ./skeleton - # highlight-add-next-line - cookiecutterCompat: true - values: + name: Fetch Base + # highlight-remove-next-line + action: fetch:cookiecutter + # highlight-add-next-line + action: fetch:template + input: + url: ./skeleton + # highlight-add-next-line + cookiecutterCompat: true + values: ``` ### Manual migration diff --git a/docs/features/software-templates/dry-run-testing.md b/docs/features/software-templates/dry-run-testing.md index dfabad265e..15c2684808 100644 --- a/docs/features/software-templates/dry-run-testing.md +++ b/docs/features/software-templates/dry-run-testing.md @@ -4,7 +4,7 @@ title: Dry Run Testing description: How to enable and implement dry run testing in actions --- -Scaffolder templates can be tested using the dry run feature of scaffolder actions. This allows you to simulate the effects of running a scaffolder action without making any actual changes to your environment, for example creating a webhook in Github. Once dry run is enabled in the scaffolder action, you can add handling to actions you use in your scaffolder templates to define how an action should operate in a dry run scenario. +Scaffolder templates can be tested using the dry run feature of scaffolder actions. This allows you to simulate the effects of running a scaffolder action without making any actual changes to your environment, for example creating a webhook in GitHub. Once dry run is enabled in the scaffolder action, you can add handling to actions you use in your scaffolder templates to define how an action should operate in a dry run scenario. ## Enabling dry run testing diff --git a/docs/features/software-templates/input-examples.md b/docs/features/software-templates/input-examples.md index dd1228290f..ef90013e49 100644 --- a/docs/features/software-templates/input-examples.md +++ b/docs/features/software-templates/input-examples.md @@ -297,7 +297,7 @@ spec: type: string steps: - - $yaml: https://github.com//example/path/to/action.yaml + - $yaml: https://github.com/example/path/to/action.yaml - id: fetch name: Fetch template diff --git a/docs/features/software-templates/migrating-to-rjsf-v5.md b/docs/features/software-templates/migrating-to-rjsf-v5.md index 856841bdd4..8357d7567b 100644 --- a/docs/features/software-templates/migrating-to-rjsf-v5.md +++ b/docs/features/software-templates/migrating-to-rjsf-v5.md @@ -44,7 +44,7 @@ It's possible that if you have a hard dependency on any of the `@rjsf/*` librari /* highlight-remove-next-line */ import { FieldValidation } from '@rjsf/core'; /* highlight-add-next-line */ -import { FieldValidation } from '@rjsf/utils; +import { FieldValidation } from '@rjsf/utils'; ``` ## Escape hatch diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 13dbe6da4b..05e238ec26 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -947,3 +947,19 @@ Have in mind that changes in this form will not be saved on the template and is ### Custom Field Explorer The custom filed explorer allows you to select any custom field loaded on the backstage instance and test different values and configurations. + +## Presentation + +You can configure the text of the "Back", "Review", and "Create" buttons using the `spec.presentation` field of your Software Template. You might want have a Template that doesn't "Create" something but rather "Updates" it. This feature will allow you to change it as needed. Here's an example of how to use this: + +```yaml +--- +spec: + owner: scaffolder/maintainers + type: website + presentation: + buttonLabels: + backButtonText: 'Return' + createButtonText: 'Update' + reviewButtonText: 'Verify' +``` diff --git a/docs/frontend-system/architecture/00-index.md b/docs/frontend-system/architecture/00-index.md index 8585eb76fc..20a12ba1e8 100644 --- a/docs/frontend-system/architecture/00-index.md +++ b/docs/frontend-system/architecture/00-index.md @@ -18,7 +18,7 @@ Regardless of whether you are setting up your own backstage instance, developing plugins, or extending plugins with new features, it is important to understand these concepts. -The diagram below provides an overview of the different building blocks, and the other blocks that each of them interact with. +The diagram below provides an overview of the different building blocks and the other blocks that each of them interacts with. ![frontend system building blocks diagram](../../assets/frontend-system/architecture-building-blocks.drawio.svg) @@ -28,11 +28,11 @@ This is the app instance itself that you create and use as the root of your Back ### Extensions -Extensions are the building blocks that build out both the visual and non-visual structure of the application. There are both built-in extensions provided by the app itself, as well as extensions provided by plugins. Each extension is attached to a parent with which it shares data, and can have any number of children of its own. It is up to the app to wire together all extensions into a single tree known as the app extension tree. It is from this structure that the entire app can then be instantiated and rendered. +Extensions are the building blocks that build out both the visual and non-visual structure of the application. There are both built-in extensions provided by the app itself as well as extensions provided by plugins. Each extension is attached to a parent with which it shares data and can have any number of children of its own. It is up to the app to wire together all extensions into a single tree known as the app extension tree. It is from this structure that the entire app can then be instantiated and rendered. ### Plugins -Plugins provide the actual features inside an app. The size of a plugin can range from a tiny component to an entire new system in which other plugins can be composed and integrated. Plugins can be completely standalone, or build on top of each other to extend existing plugins and augment their features. Plugins can communicate with each other by composing their extensions, or by sharing Utility APIs and routes. +Plugins provide the actual features inside an app. The size of a plugin can range from a tiny component to an entire new system in which other plugins can be composed and integrated. Plugins can be completely standalone or built on top of each other to extend existing plugins and augment their features. Plugins can communicate with each other by composing their extensions or by sharing Utility APIs and routes. ### Extension Overrides @@ -40,11 +40,11 @@ In addition to the built-in extensions and extensions provided by plugins, it is ### Utility APIs -Utility APIs provide functionality that makes it easier to build plugins, make it possible for plugins to share functionality with other plugins, as well as serve as a customization point for integrators to change the behavior of the app. Each Utility API is defined by a TypeScript interface as well as a reference used to access the implementations. The implementations of Utility APIs are defined by extensions that are provided and can be overridden the same as any other extension. +Utility APIs provide functionality that makes it easier to build plugins, make it possible for plugins to share functionality with other plugins, as well as serve as a customization point for integrators to change the behaviour of the app. Each Utility API is defined by a TypeScript interface as well as a reference used to access the implementations. The implementations of Utility APIs are defined by extensions that are provided and can be overridden the same as any other extension. ### Routes -The Backstage routing system adds a layer of indirection that makes it possible for plugins to route to each other's extensions without explicit knowledge of what URL paths the extension are rendered at or if they even exist at all. It makes it possible for plugins to share routes with each other and dynamically generate concrete links at runtime. It is the responsibility of the app to resolve these links to actual URLs, but it is also possible for integrators to define their own route bindings that decide how the links should be resolved. The routing system also lets plugins define internal routes, aiding in the linking to different content in the same plugin. +The Backstage routing system adds a layer of indirection that makes it possible for plugins to route to each other's extensions without explicit knowledge of what URL paths the extensions are rendered at or if they even exist at all. It makes it possible for plugins to share routes with each other and dynamically generate concrete links at runtime. It is the responsibility of the app to resolve these links to actual URLs, but it is also possible for integrators to define their own route bindings that decide how the links should be resolved. The routing system also lets plugins define internal routes, aiding in the linking to different content in the same plugin. ## Package structure diff --git a/docs/frontend-system/architecture/23-extension-blueprints.md b/docs/frontend-system/architecture/23-extension-blueprints.md index ee09e7fec6..fb10749845 100644 --- a/docs/frontend-system/architecture/23-extension-blueprints.md +++ b/docs/frontend-system/architecture/23-extension-blueprints.md @@ -20,7 +20,7 @@ The following is a simple example of how one might use the blueprint `make` meth const myPageExtension = PageBlueprint.make({ params: { defaultPath: '/my-page', - loader: () => import('./components/MyPage').them(m => ), + loader: () => import('./components/MyPage').then(m => ), }, }); ``` @@ -47,7 +47,7 @@ const myPageExtension = PageBlueprint.makeWithOverrides({ return originalFactory({ defaultPath: '/my-page', loader: () => - import('./components/MyPage').them(m => ( + import('./components/MyPage').then(m => ( // We can now access values from the factory context when providing // the blueprint parameters, such as config values. @@ -104,7 +104,7 @@ Most of the options provided to `createExtensionBlueprint` can be overridden whe In some cases you may want to define and provide [extension data reference](./20-extensions.md#extension-data-references) that are specific to your blueprint. In the above example we might want to forward the `title` as data for example, rather than encapsulating it into the `MyWidgetContainer` component. This gives the parent extension more flexibility in the rendering for our example widget extensions. -To do that, we create a new extension data reference for our widget title. This references is provided via the `dataRefs` options when we create the blueprint, which makes it available for use via `MyWidgetBlueprint.dataRefs.widgetTitle`. +To do that, we create a new extension data reference for our widget title. This reference is provided via the `dataRefs` options when we create the blueprint, which makes it available for use via `MyWidgetBlueprint.dataRefs.widgetTitle`. ```tsx export interface MyWidgetBlueprintParams { diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 785843f6e0..0da2ed6eef 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -263,23 +263,19 @@ const overrideExtension = exampleExtension.override({ To install extension overrides in a Backstage app you should use `plugin.withOverrides` whenever you are overriding or adding extensions for a plugin. See the section on [overriding a plugin](./15-plugins.md#overriding-a-plugin) for more information. -There is also a `createExtensionOverrides` function that can be used to install a collection of standalone extensions in an app. This method will be replaced with a different mechanism in the future, but for now remains the only way to override the built-in extensions in the app or to package extensions for a plugin package separate from the plugin itself. - Note that while using either of these options you don't necessarily need to use the extension `.override(...)` method to create the overrides. You can also create new extensions with `createExtension` or a blueprint that are either completely net-new extensions, or override an existing extension by using the same `kind`, `namespace` and `name` to produce the same extension ID. -### Creating a standalone extension bundle +### Creating a frontend module -The following example shows how to create a standalone extension bundle that overrides the search page from the search plugin: +The following example shows how to create a frontend module that overrides the search page from the search plugin: ```tsx import { createPageExtension, - createExtensionOverrides, + createFrontendModule, } from '@backstage/frontend-plugin-api'; const customSearchPage = PageBlueprint.make({ - // Since this is a standalone extension we need to provide the namespace to match the search plugin - namespace: 'search', params: { defaultPath: '/search', loader: () => @@ -287,7 +283,8 @@ const customSearchPage = PageBlueprint.make({ }, }); -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'search', extensions: [customSearchPage], }); ``` @@ -296,12 +293,14 @@ Assuming the above code resides in the `@internal/search-page` package, you can ```tsx title="packages/app/src/App.tsx" import { createApp } from '@backstage/frontend-defaults'; -import searchPageOverride from '@internal/search-page'; +import searchPageModule from '@internal/search-page'; const app = createApp({ // highlight-next-line - features: [searchPageOverride], + features: [searchPageModule], }); export default app.createRoot(); ``` + +You must define a `pluginId` when creating a frontend module, and the plugin must also be installed for the module to be loaded. diff --git a/docs/frontend-system/architecture/60-migrations.md b/docs/frontend-system/architecture/60-migrations.md index a1ab0197f7..3687106600 100644 --- a/docs/frontend-system/architecture/60-migrations.md +++ b/docs/frontend-system/architecture/60-migrations.md @@ -14,6 +14,60 @@ This section provides migration guides for different versions of the frontend sy This guide is intended for app and plugin authors who have already migrated their code to the new frontend system, and are looking to keep it up to date with the latest changes. These guides do not cover trivial migrations that can be explained in a deprecation message, such as a renamed export. +## 1.31.0 + +### `namespace` parameter should be removed + +The `namespace` parameter to all `createExtension`, `createExtensionBlueprint` or any `.make()` or `.makeWithOverrides()` method can be removed. This will now default to the `id` of the `plugin` which should already be the case. + +### `createExtensionOverrides` -> `createFrontendModule` + +Extensions will need to be wrapped in a module in order to be installed in the frontend. This means that the `createExtensionOverrides` function should be replaced with `createFrontendModule`, and the `pluginId` parameter should be set with the target `pluginId` that you're overriding. + +For example: + +```tsx +import { + createPageExtension, + createExtensionOverrides, +} from '@backstage/frontend-plugin-api'; + +const customSearchPage = PageBlueprint.make({ + namespace: 'search', + params: { + defaultPath: '/search', + loader: () => + import('./CustomSearchPage').then(m => ), + }, +}); + +export default createExtensionOverrides({ + extensions: [customSearchPage], +}); +``` + +Should now look like this: + +```tsx +import { + createPageExtension, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; + +const customSearchPage = PageBlueprint.make({ + params: { + defaultPath: '/search', + loader: () => + import('./CustomSearchPage').then(m => ), + }, +}); + +export default createFrontendModule({ + pluginId: 'search', + extensions: [customSearchPage], +}); +``` + ## 1.30 ### Reworked extension inputs and outputs diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 96e7dd2bd5..6efd0c7841 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -95,7 +95,7 @@ At this point the contents of your app should be past the initial migration stag ## Migrating `createApp` Options -Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension blueprint](../architecture/23-extension-blueprints.md) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createExtensionOverrides`, which bundles them into a _feature_ that you can install in the app. See the [standalone extensions](../architecture/25-extension-overrides.md#creating-a-standalone-extension-bundle) section for more information. +Many of the `createApp` options have been migrated to use extensions instead. Each will have their own [extension blueprint](../architecture/23-extension-blueprints.md) that you use to create a custom extension. To add these standalone extensions to the app they need to be passed to `createFrontendModule`, which bundles them into a _feature_ that you can install in the app. See the [frontend module](../architecture/25-extension-overrides.md#creating-a-frontend-module) section for more information. For example, assuming you have a `lightTheme` extension that you want to add to your app, you can use the following: @@ -103,7 +103,8 @@ For example, assuming you have a `lightTheme` extension that you want to add to const app = createApp({ features: [ // highlight-add-start - createExtensionOverrides({ + createFrontendModule({ + pluginId: 'app', extensions: [lightTheme], }), // highlight-add-end @@ -287,6 +288,7 @@ const app = createApp({ ), }, ], +}); ``` Can be converted to the following extension: @@ -342,7 +344,8 @@ const exampleIconBundle = IconBundleBlueprint.make({ const app = createApp({ features: [ - createExtensionOverrides({ + createFrontendModule({ + pluginId: 'app', extensions: [exampleIconBundle], }), ], @@ -567,7 +570,8 @@ Here is an example converting the `CustomAppBarrier` into extension: createApp({ // ... features: [ - createExtensionOverrides({ + createFrontendModule({ + pluginId: 'app', extensions: [ AppRootWrapperBlueprint.make({ name: 'custom-app-barrier', diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 57a783d406..633afd6ce8 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -64,8 +64,8 @@ export const rootRouteRef = createRouteRef(); ```tsx title="in src/plugin.ts" import { createFrontendPlugin, - createPageExtension, - createNavItemExtension, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from './routes'; @@ -155,10 +155,7 @@ export function ExamplePage() { ``` ```tsx title="in src/plugin.ts - Registering a factory for our API" -import { - createApiFactory, - createApiExtension, -} from '@backstage/frontend-plugin-api'; +import { createApiFactory, ApiBlueprint } from '@backstage/frontend-plugin-api'; import { exampleApiRef, DefaultExampleApi } from './api'; // highlight-add-start diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index e403104506..4847fc6eaf 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -41,13 +41,14 @@ Like with other extension types, you replace Utility APIs with your own custom i ```tsx title="in your app" /* highlight-add-start */ -import { createExtensionOverrides } from '@backstage/frontend-plugin-api'; +import { createFrontendModule } from '@backstage/frontend-plugin-api'; class CustomWorkImpl implements WorkApi { /* ... */ } -const myOverrides = createExtensionOverrides({ +const workModule = createFrontendModule({ + pluginId: 'work', extensions: [ ApiBlueprint.make({ params: { @@ -66,7 +67,7 @@ export default createApp({ features: [ // ... other features /* highlight-add-next-line */ - myOverrides, + workModule, ], }); ``` diff --git a/docs/getting-started/config/authentication.md b/docs/getting-started/config/authentication.md index e97ed3d89c..2442d2b767 100644 --- a/docs/getting-started/config/authentication.md +++ b/docs/getting-started/config/authentication.md @@ -1,50 +1,54 @@ --- id: authentication title: Authentication -description: How to set up authentication into your Backstage installation +description: How to setup authentication for your Backstage app --- Audience: Admins or Developers -### Setting up authentication +## Summary -There are multiple authentication providers available for you to use with -Backstage, feel free to follow -[the instructions for adding authentication](../../auth/index.md). +We'll be walking you through how to setup authentication for your Backstage app using GitHub. After finishing this guide, you'll have both working authentication and users in your Backstage app to match to the users logging in! -For this tutorial we choose to use GitHub, a free service most of you might be -familiar with. For other options, see -[the auth provider documentation](../../auth/github/provider.md#create-an-oauth-app-on-github). +There are multiple authentication providers available for you to use with Backstage, feel free to follow [their instructions for adding authentication](../../auth/index.md). -Go to -[https://github.com/settings/applications/new](https://github.com/settings/applications/new) -to create your OAuth App. The `Homepage URL` should point to Backstage's -frontend, in our tutorial it would be `http://localhost:3000`. The -`Authorization callback URL` will point to the auth backend, which will most -likely be `http://localhost:7007/api/auth/github/handler/frame`. +:::note Note + +The default Backstage app comes with a guest Sign In Resolver. This resolver makes all users share a single "guest" identity and is only intended as a minimum requirement to quickly get up and running. You can read more about how [Sign In Resolvers](../../auth/identity-resolver.md#sign-in-resolvers) play a role in creating a [Backstage User Identity](../../auth/identity-resolver.md#backstage-user-identity) for logged in users. + +::: + +## Setting up authentication + +For this tutorial we choose to use GitHub, a free service most of you might be familiar with, and we'll be using an OAuth app. For detailed options, see +[the GitHub auth provider documentation](../../auth/github/provider.md#create-an-oauth-app-on-github). + +Go to [https://github.com/settings/applications/new](https://github.com/settings/applications/new) to create your OAuth App. The "Homepage URL" should point to Backstage's frontend, in our tutorial it would be `http://localhost:3000`. The "Authorization callback URL" will point to the auth backend, which will most likely be `http://localhost:7007/api/auth/github/handler/frame`. ![Screenshot of the GitHub OAuth creation page](../../assets/getting-started/gh-oauth.png) -Take note of the `Client ID` and the `Client Secret`. Open `app-config.yaml`, -and add your `clientId` and `clientSecret` to this file. It should end up -looking like this: +Take note of the `Client ID` and the `Client Secret` (clicking the "Generate a new client secret" button will get this value for you). Open `app-config.yaml`, and add them as `clientId` and `clientSecret` in this file. It should end up looking like this: ```yaml title="app-config.yaml" auth: # see https://backstage.io/docs/auth/ to learn about auth providers + /* highlight-add-start */ environment: development + /* highlight-add-end */ providers: + # See https://backstage.io/docs/auth/guest/provider + guest: {} + /* highlight-add-start */ github: development: clientId: YOUR CLIENT ID clientSecret: YOUR CLIENT SECRET + /* highlight-add-end */ ``` -### Add sign-in option to the frontend +## Add sign-in option to the frontend -Backstage will re-read the configuration. If there's no errors, that's great! We -can continue with the last part of the configuration. The next step is needed to -change the sign-in page, this you actually need to add in the source code. +The next step is to change the sign-in page. For this, you'll actually need to write some code. Open `packages/app/src/App.tsx` and below the last `import` line, add: @@ -71,13 +75,52 @@ components: { }, ``` -:::note Note +## Add sign-in resolver(s) -The default Backstage app comes with a guest Sign In Resolver. This resolver makes all users share a single "guest" identity and is only intended as a minimum requirement to quickly get up and running. You can read more about how [Sign In Resolvers](../../auth/identity-resolver.md#sign-in-resolvers) play a role in creating a [Backstage User Identity](../../auth/identity-resolver.md#backstage-user-identity) for logged in users. +Next we need to add the sign-in resolver to our configuration. Here's how: -::: +```yaml title="app-config.yaml" +auth: + # see https://backstage.io/docs/auth/ to learn about auth providers + environment: development + providers: + # See https://backstage.io/docs/auth/guest/provider + guest: {} + github: + development: + clientId: YOUR CLIENT ID + clientSecret: YOUR CLIENT SECRET + /* highlight-add-start */ + signIn: + resolvers: + # Matches the GitHub username with the Backstage user entity name. + # See https://backstage.io/docs/auth/github/provider#resolvers for more resolvers. + - resolver: usernameMatchingUserEntityName + /* highlight-add-end */ +``` -Restart Backstage from the terminal, by stopping it with `Control-C`, and starting it with `yarn dev` . You should be welcomed by a login prompt! +What this will do is take the user details provided by the auth provider and match that against a User in the Catalog. In this case - `usernameMatchingUserEntityName` - will match the GitHub user name with the `metadata.name` value of a User in the Catalog, if none is found you will get an "Failed to sign-in, unable to resolve user identity" message. We'll cover this in the next few sections. + +Learn more about this topic in the [Sign-in Resolvers](../../auth/identity-resolver.md#sign-in-resolvers) documentation. + +## Add the auth provider to the backend + +To add the auth provider to the backend, we will first need to install the package by running this command: + +```bash title="from your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-github-provider +``` + +Then we will need to add this line: + +```ts title="in packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-auth-backend')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); +/* highlight-add-end */ +``` + +Restart Backstage from the terminal, by stopping it with `Ctrl+C`, and starting it with `yarn dev`. You should be welcomed by a login prompt! If you try to login at this point you will get a "Failed to sign-in, unable to resolve user identity" message, read on as we'll fix that next. :::note Note @@ -85,33 +128,46 @@ Sometimes the frontend starts before the backend resulting in errors on the sign ::: +## Adding a User + +The recommended approach for adding Users, and Groups, into your Catalog is to use one of the existing Org Entity Providers - [like this one for GitHub](https://backstage.io/docs/integrations/github/org) - or if those don't work you may need to [create one](https://backstage.io/docs/features/software-catalog/external-integrations#custom-entity-providers) that fits your Organization's needs. + +For the sake of this guide we'll simply step you though adding a User to the `org.yaml` file that is included when you create a new Backstage instance. Let's do that: + +1. First open the `/examples/org.yaml` file in your text editor of choice +2. At the bottom we'll add the following YAML: + + ```yaml + --- + apiVersion: backstage.io/v1alpha1 + kind: User + metadata: + name: YOUR GITHUB USERNAME + spec: + memberOf: [guests] + ``` + +3. Now make sure to replace the text "YOUR GITHUB USERNAME" with your actual GitHub User name. + +Let's restart Backstage from the terminal once more, by stopping it with `Ctrl+C`, and starting it with `yarn dev`. You should now be able to log into Backstage and see items in your Catalog. + To learn more about Authentication in Backstage, here are some docs you could read: - [Authentication in Backstage](../../auth/index.md) - [Using organizational data from GitHub](../../integrations/github/org.md) -### Setting up a GitHub Integration +## Setting up a GitHub Integration -The GitHub integration supports loading catalog entities from GitHub or GitHub -Enterprise. Entities can be added to static catalog configuration, registered -with the catalog-import plugin, or discovered from a GitHub organization. Users -and Groups can also be loaded from an organization. While using [GitHub Apps](../../integrations/github/github-apps.md) -might be the best way to set up integrations, for this tutorial you'll use a -Personal Access Token. +The GitHub integration supports loading catalog entities from GitHub or GitHub Enterprise. Entities can be added to static catalog configuration, registered with the catalog-import plugin, or discovered from a GitHub organization. Users and Groups can also be loaded from an organization. While using [GitHub Apps](../../integrations/github/github-apps.md) might be the best way to set up integrations, for this tutorial you'll use a Personal Access Token. -Create your Personal Access Token by opening -[the GitHub token creation page](https://github.com/settings/tokens/new). Use a -name to identify this token and put it in the notes field. Choose a number of -days for expiration. If you have a hard time picking a number, we suggest to go -for 7 days, it's a lucky number. +Create your Personal Access Token by opening [the GitHub token creation page](https://github.com/settings/tokens/new). Use a name to identify this token and put it in the notes field. Choose a number of days for expiration. If you have a hard time picking a number, we suggest to go for 7 days, it's a lucky number. ![Screenshot of the GitHub Personal Access Token creation page](../../assets/getting-started/gh-pat.png) Set the scope to your likings. For this tutorial, selecting `repo` and `workflow` is required as the scaffolding job in this guide configures a GitHub actions workflow for the newly created project. -For this tutorial, we will be writing the token to `app-config.local.yaml`. This file might not exist for you, so if it doesn't go ahead and create it alongside the `app-config.yaml` at the root of the project. -This file should also be excluded in `.gitignore`, to avoid accidental committing of this file. +For this tutorial, we will be writing the token to `app-config.local.yaml`. This file might not exist for you, so if it doesn't go ahead and create it alongside the `app-config.yaml` at the root of the project. This file should also be excluded in `.gitignore`, to avoid accidental committing of this file. More details on this file can be found in the [Static Configuration documentation](../../conf/index.md). In your `app-config.local.yaml` go ahead and add the following: diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 8bb30d1db8..0d5529f611 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -23,11 +23,11 @@ If you are planning to contribute a new feature or bug fix to the Backstage proj This guide assumes a basic understanding of working on a Linux based operating system and have some experience with the terminal, specifically, these commands: `npm`, `yarn`. -- Access to a Unix-based operating system, such as Linux, MacOS or +- Access to a Unix-based operating system, such as Linux, macOS or [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) - A GNU-like build environment available at the command line. For example, on Debian/Ubuntu you will want to have the `make` and `build-essential` packages installed. - On MacOS, you will want to have run `xcode-select --install` to get the XCode command line build tooling in place. + On macOS, you will want to have run `xcode-select --install` to get the XCode command line build tooling in place. - An account with elevated rights to install the dependencies - `curl` or `wget` installed - Node.js [Active LTS Release](https://nodejs.org/en/about/previous-releases) installed using one of these @@ -111,7 +111,7 @@ If this fails on the `yarn install` step, it's likely that you will need to inst ## 2. Run the Backstage app -You Backstage app is fully installed and ready to be run! Now that the installation is complete, you can go to the application directory and start the app using the `yarn dev` command. The `yarn dev` command will run both the frontend and backend as separate processes (named `[0]` and `[1]`) in the same window. +Your Backstage app is fully installed and ready to be run! Now that the installation is complete, you can go to the application directory and start the app using the `yarn dev` command. The `yarn dev` command will run both the frontend and backend as separate processes (named `[0]` and `[1]`) in the same window. ```bash cd my-backstage-app # your app name diff --git a/docs/getting-started/logging-in.md b/docs/getting-started/logging-in.md index 7d6fb5d55b..16596865a2 100644 --- a/docs/getting-started/logging-in.md +++ b/docs/getting-started/logging-in.md @@ -12,7 +12,7 @@ This guide will provide a quick tutorial on how to log in to your Backstage inst ## Prerequisites -You should have already [have a standalone app](./index.md) and completed the Github OAuth app setup defined in [the authentication tutorial](./config/authentication.md). +You should have already [have a standalone app](./index.md) and completed the GitHub OAuth app setup defined in [the authentication tutorial](./config/authentication.md). ## 1. Login to Backstage @@ -22,13 +22,13 @@ If you're not already logged in, you should see a login screen like this, ![Screenshot of the login screen](../assets/getting-started/login-screen.png) -To login, you should choose the "Github" provider and click the "Sign in" button. This will redirect you to a Github OAuth page. Verify that the scopes mentioned on that page match the setup you did in [the authentication tutorial](./config/authentication.md). Once you click "Confirm", you will be brought back to the Backstage interface and signed in! +To login, you should choose the "GitHub" provider and click the "Sign in" button. This will redirect you to a GitHub OAuth page. Verify that the scopes mentioned on that page match the setup you did in [the authentication tutorial](./config/authentication.md). Once you click "Confirm", you will be brought back to the Backstage interface and signed in! If you are already logged in, you will be automatically brought to your Backstage instance. ## 2. Verify that you're logged in -Once you've logged in, find the "Settings" item in the navigation bar to the left. Click it and you will see your profile. If you see your profile picture and name from Github here, congratulations! You've successfully set up a Github authentication integration. +Once you've logged in, find the "Settings" item in the navigation bar to the left. Click it and you will see your profile. If you see your profile picture and name from GitHub here, congratulations! You've successfully set up a GitHub authentication integration. diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index 24b64d1116..aa1b49b41c 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -171,7 +171,10 @@ catalog: host: gitlab.com orgEnabled: true group: org/teams # Required for gitlab.com when `orgEnabled: true`. Optional for self managed. Must not end with slash. Accepts only groups under the provided path (which will be stripped) - allowInherited: true # Allow groups to be ingested even if there are no direct members. + relations: # Optional + - INHERITED # Optional. Members of any ancestor groups will also be considered members of the current group. + - DESCENDANTS # Optional. Members of any descendant groups will also be considered members of the current group. + - SHARED_FROM_GROUPS # Optional. Members of any invited groups will also be considered members of the current group. groupPattern: '[\s\S]*' # Optional. Filters found groups based on provided pattern. Defaults to `[\s\S]*`, which means to not filter anything schedule: # Same options as in SchedulerServiceTaskScheduleDefinition. Optional for the Legacy Backend System. # supports cron, ISO duration, "human duration" as used in code @@ -193,6 +196,32 @@ entities will only be ingested for the configured group, or its descendant group but not any ancestor groups higher than the configured group path. Only groups which contain members will be ingested. +### Subgroup Membership + +GitLab groups and subgroups provide a hierarchical structure for organizing projects and users. Membership in a parent group extends automatically to its subgroups, ensuring consistent permissions at all levels. Additionally, membership can be managed using invited groups, where one group can be added to another. For Backstage users integrating with GitLab, understanding this [inheritance model](https://docs.gitlab.co.jp/ee/user/group/subgroups/#subgroup-membership) and the concept of invited groups is crucial for accurately mapping and managing group and user entities. + +The `GitLabOrgDiscoveryEntityProvider` mirrors GitLab's membership behavior as follows: + +- By default, every direct member of a GitLab group is also a member of the corresponding group in Backstage. +- To include members of subgroups as members of the parent group, configure the `relations` array with the `DESCENDANTS` option. +- To include members of parent groups as members of their subgroups, configure the `relations` array with the `INHERITED` option. This also has the effect that subgroups with no direct members will not be skipped in the group ingestion process and will be added as a group entity in Backstage; +- To include members of invited groups as members of the inviting group, configure the `relations` array with the `SHARED_FROM_GROUPS` option. + +The previous `allowInherited` will be deprecated in future versions. Use the `relations` array with the `INHERITED` option instead. + +```yaml +catalog: + providers: + gitlab: + development: + relations: + - INHERITED + - DESCENDANTS + - SHARED_FROM_GROUPS +``` + +Refer to the [GitLab Group Member Relation](https://docs.gitlab.com/ee/api/graphql/reference/#groupmemberrelation) documentation for more information. + ### Users For self hosted, all `User` entities are ingested from the entire instance by default. diff --git a/docs/notifications/index.md b/docs/notifications/index.md index 86a67ce211..d4234b494e 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -33,7 +33,7 @@ Example of use-cases: ## Installation in Older Environments -Newer versions of instances created by the create-app have both the notifications and signals plugins included by default, this section can be skipped right to the Configuration. +Newer versions of instances created by the create-app have both the notifications and signals plugins included by default; this section can be skipped right to the Configuration. Following installation instructions are valid for enabling the plugins in older environments. @@ -136,15 +136,15 @@ If the signals plugin is properly configured, it will be automatically discovere 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. +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. +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. +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. diff --git a/docs/openapi/definitions/auth.yaml b/docs/openapi/definitions/auth.yaml index 1ed649da9c..07b18866c6 100644 --- a/docs/openapi/definitions/auth.yaml +++ b/docs/openapi/definitions/auth.yaml @@ -9,7 +9,7 @@ info: The purpose of the Auth APIs in Backstage are to identify the user, and to provide a way for plugins to request access to 3rd party services on behalf of that user. - The API is supplied with a list of providers - such as `Google` or `Github` - and will add the endpoints + The API is supplied with a list of providers - such as `Google` or `GitHub` - and will add the endpoints described below to each of those providers. Read more about [User Authentication and Authorization in Backstage](https://github.com/backstage/backstage/blob/master/docs/auth/overview.md). diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index 74c055a86c..377e546024 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -79,7 +79,7 @@ By default all internal users are allowed to create and delete entities. If this By default, Scaffolding jobs execute directly on the host machine, including any actions defined in the template. Because the Scaffolder templates are considered a more sensitive area it is recommended to control access to create and update templates to trusted parties. Template execution is intended to be secure regardless of input, but we still recommend this additional layer of protection. The string templating is executed in a [node VM sandbox](https://github.com/laverdet/isolated-vm) to mitigate the possibility of remote code execution attacks. -The Scaffolder often has elevated permissions to for example create repositories in a Github organization. The operator should therefore be cautious of Scaffolder Templates that for example delete or update existing resources as the user input is typically user defined and can therefore delete or modify resources maliciously or by mistake. +The Scaffolder often has elevated permissions to for example create repositories in a GitHub organization. The operator should therefore be cautious of Scaffolder Templates that for example delete or update existing resources as the user input is typically user defined and can therefore delete or modify resources maliciously or by mistake. One strategy that allows you to reduce the access that the Scaffolder service has is to rely on user credentials when executing actions. For example, a GitHub App integration could be configured with read-only permissions, with a separate user OAuth token used to create repositories. This requires that your users have access to create repositories in the first place. diff --git a/docs/permissions/plugin-authors/01-setup--old.md b/docs/permissions/plugin-authors/01-setup--old.md new file mode 100644 index 0000000000..34802d004f --- /dev/null +++ b/docs/permissions/plugin-authors/01-setup--old.md @@ -0,0 +1,134 @@ +--- +id: 01-setup--old +title: 1. Tutorial setup +description: How to get started with the permission framework as a plugin author +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./01-setup.md) instead. Otherwise, [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). + +## Setup for the Tutorial + +We will use a "Todo list" feature, composed of the `todo-list` and `todo-list-backend` plugins, as well as their dependency, `todo-list-common`. + +The source code is available here: + +- [todo-list](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list) +- [todo-list-backend](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-backend) +- [todo-list-common](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-common) + +1. Copy-paste the three folders into the plugins folder of your backstage application repository (removing the `example-` prefix from each folder) or run the following script from the root of your backstage application: + + ```bash + $ cd $(mktemp -d) + git clone --depth 1 --quiet --no-checkout --filter=blob:none https://github.com/backstage/backstage.git . + git checkout master -- plugins/example-todo-list/ + git checkout master -- plugins/example-todo-list-backend/ + git checkout master -- plugins/example-todo-list-common/ + sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json + sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json + sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json + for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done + cd - + ``` + + The `plugins` directory of your project should now include `todo-list`, `todo-list-backend`, and `todo-list-common`. + + **Important**: if you are on **Windows**, make sure you have WSL and git installed on your machine before executing the script above. + +2. Add these packages as dependencies for your Backstage app: + + ```sh title="From your Backstage root directory" + yarn --cwd packages/backend add @internal/plugin-todo-list-backend @internal/plugin-todo-list-common + yarn --cwd packages/app add @internal/plugin-todo-list + ``` + +3. Include the backend and frontend plugin in your application: + + Create a new `packages/backend/src/plugins/todolist.ts` with the following content: + + ```typescript title="packages/backend/src/plugins/todolist.ts" + import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + import { createRouter } from '@internal/plugin-todo-list-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin({ + logger, + discovery, + }: PluginEnvironment): Promise { + return await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), + }); + } + ``` + + Apply the following changes to `packages/backend/src/index.ts`: + + ```ts title="packages/backend/src/index.ts" + import techdocs from './plugins/techdocs'; + /* highlight-add-next-line */ + import todoList from './plugins/todolist'; + import search from './plugins/search'; + + async function main() { + const searchEnv = useHotMemoize(module, () => createEnv('search')); + const appEnv = useHotMemoize(module, () => createEnv('app')); + /* highlight-add-next-line */ + const todoListEnv = useHotMemoize(module, () => createEnv('todolist')); + // .. + + apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/search', await search(searchEnv)); + apiRouter.use('/permission', await permission(permissionEnv)); + /* highlight-add-next-line */ + apiRouter.use('/todolist', await todoList(todoListEnv)); + // Add backends ABOVE this line; this 404 handler is the catch-all fallback + apiRouter.use(notFoundHandler()); + // .. + } + ``` + + Apply the following changes to `packages/app/src/App.tsx`: + + ```tsx title="packages/app/src/App.tsx" + /* highlight-add-next-line */ + import { TodoListPage } from '@internal/plugin-todo-list'; + + const routes = ( + + }> + {searchPage} + + } /> + {/* highlight-add-next-line */} + } /> + {/* ... */} + + ); + ``` + +Now if you start your application you should be able to reach the `/todo-list` page: + +![Todo List plugin page](../../assets/permissions/permission-todo-list-page.png) + +--- + +## Integrate the new plugin + +If you play with the UI, you will notice that it is possible to perform a few actions: + +- create a new todo item (`POST /todos`) +- view todo items (`GET /todos`) +- edit an existing todo item (`PUT /todos`) + +Let's try to bring authorization on top of each one of them. diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md index f11bdff930..f3c361e8b5 100644 --- a/docs/permissions/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -4,6 +4,10 @@ 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](./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). @@ -40,58 +44,31 @@ The source code is available here: 2. Add these packages as dependencies for your Backstage app: ```sh title="From your Backstage root directory" - yarn --cwd packages/backend add @internal/plugin-todo-list-backend @internal/plugin-todo-list-common + yarn --cwd packages/backend add @backstage/plugin-permission-backend @backstage/plugin-permission-backend-module-allow-all-policy @internal/plugin-todo-list-backend @internal/plugin-todo-list-common yarn --cwd packages/app add @internal/plugin-todo-list ``` 3. Include the backend and frontend plugin in your application: - Create a new `packages/backend/src/plugins/todolist.ts` with the following content: - - ```typescript title="packages/backend/src/plugins/todolist.ts" - import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; - import { createRouter } from '@internal/plugin-todo-list-backend'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - export default async function createPlugin({ - logger, - discovery, - }: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - }); - } - ``` - Apply the following changes to `packages/backend/src/index.ts`: ```ts title="packages/backend/src/index.ts" - import techdocs from './plugins/techdocs'; - /* highlight-add-next-line */ - import todoList from './plugins/todolist'; - import search from './plugins/search'; - - async function main() { - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const todoListEnv = useHotMemoize(module, () => createEnv('todolist')); - // .. - - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - apiRouter.use('/permission', await permission(permissionEnv)); - /* highlight-add-next-line */ - apiRouter.use('/todolist', await todoList(todoListEnv)); - // Add backends ABOVE this line; this 404 handler is the catch-all fallback - apiRouter.use(notFoundHandler()); - // .. - } + import { createBackend } from '@backstage/backend-defaults'; + //... + const backend = createBackend(); + //... + /* highlight-add-start */ + // Installing the permission plugin + backend.add(import('@backstage/plugin-permission-backend/alpha')); + // Installing the allow all permission policy module + backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), + ); + // Installing the todolist plugin + backend.add(import('@internal/plugin-todo-list-backend')); + /* highlight-add-end */ + //... + backend.start(); ``` Apply the following changes to `packages/app/src/App.tsx`: @@ -99,13 +76,11 @@ The source code is available here: ```tsx title="packages/app/src/App.tsx" /* highlight-add-next-line */ import { TodoListPage } from '@internal/plugin-todo-list'; + //... const routes = ( - }> - {searchPage} - - } /> + {/* ... */} {/* highlight-add-next-line */} } /> {/* ... */} diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md new file mode 100644 index 0000000000..7c8dad3d87 --- /dev/null +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md @@ -0,0 +1,387 @@ +--- +id: 02-adding-a-basic-permission-check--old +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 old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./02-adding-a-basic-permission-check.md) instead. Otherwise, [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). + +We'll start by creating a new permission, and then we'll use the permission api to call `authorize` with it during todo creation. + +## Creating a new permission + +Let's navigate to the file `plugins/todo-list-common/src/permissions.ts` and add our first permission: + +```ts title="plugins/todo-list-common/src/permissions.ts" +import { createPermission } from '@backstage/plugin-permission-common'; + +/* highlight-remove-start */ +export const tempExamplePermission = createPermission({ + name: 'temp.example.noop', + attributes: {}, +/* highlight-remove-end */ +/* highlight-add-start */ +export const todoListCreatePermission = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, +/* highlight-add-end */ +}); + +/* highlight-remove-next-line */ +export const todoListPermissions = [tempExamplePermission]; +/* highlight-add-next-line */ +export const todoListPermissions = [todoListCreatePermission]; +``` + +For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`). + +:::note Note + +We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/tooling/cli/build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies. + +::: + +## Authorizing using the new permission + +Install the following module: + +``` +$ yarn workspace @internal/plugin-todo-list-backend \ + add @backstage/plugin-permission-common @backstage/plugin-permission-node @internal/plugin-todo-list-common +``` + +Edit `plugins/todo-list-backend/src/service/router.ts`: + +```ts title="plugins/todo-list-backend/src/service/router.ts" +/* highlight-remove-start */ +import { InputError } from '@backstage/errors'; +import { IdentityApi } from '@backstage/plugin-auth-node'; +/* highlight-remove-end */ +/* highlight-add-start */ +import { InputError, NotAllowedError } from '@backstage/errors'; +import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; +import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-end */ + +export interface RouterOptions { + logger: Logger; + identity: IdentityApi; + /* highlight-add-next-line */ + permissions: PermissionEvaluator; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + /* highlight-remove-next-line */ + const { logger, identity } = options; + /* highlight-add-next-line */ + const { logger, identity, permissions } = options; + + /* highlight-add-start */ + const permissionIntegrationRouter = createPermissionIntegrationRouter({ + permissions: [todoListCreatePermission], + }); + /* highlight-add-end */ + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.json({ status: 'ok' }); + }); + + /* highlight-add-next-line */ + router.use(permissionIntegrationRouter); + + router.get('/todos', async (_req, res) => { + res.json(getAll()); + }); + + router.post('/todos', async (req, res) => { + let author: string | undefined = undefined; + + const user = await identity.getIdentity({ request: req }); + author = user?.identity.userEntityRef; + /* highlight-add-start */ + const token = getBearerTokenFromAuthorizationHeader( + req.header('authorization'), + ); + const decision = ( + await permissions.authorize([{ permission: todoListCreatePermission }], { + token, + }) + )[0]; + + if (decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + /* highlight-add-end */ + + if (!isTodoCreateRequest(req.body)) { + throw new InputError('Invalid payload'); + } + + const todo = add({ title: req.body.title, author }); + res.json(todo); + }); + + // ... +``` + +Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`: + +```ts title="packages/backend/src/plugins/todolist.ts" +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +import { createRouter } from '@internal/plugin-todo-list-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + discovery, + /* highlight-add-next-line */ + permissions, +}: PluginEnvironment): Promise { + return await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), + /* highlight-add-next-line */ + permissions, + }); +} +``` + +That's it! Now your plugin is fully configured. Let's try to test the logic by denying the permission. + +## Test the authorized create endpoint + +Before running this step, please make sure you followed the steps described in [Getting started](../getting-started.md) section. + +In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission: + +```ts title="packages/backend/src/plugins/permission.ts" +import { + PermissionPolicy, + /* highlight-add-start */ + PolicyQuery, + PolicyQueryUser, + /* highlight-add-end */ +} from '@backstage/plugin-permission-node'; +/* highlight-add-start */ +import { isPermission } from '@backstage/plugin-permission-common'; +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-end */ + +class TestPermissionPolicy implements PermissionPolicy { + /* highlight-remove-next-line */ + async handle(): Promise { + /* highlight-add-start */ + async handle( + request: PolicyQuery, + _user?: PolicyQueryUser, + ): Promise { + if (isPermission(request.permission, todoListCreatePermission)) { + return { + result: AuthorizeResult.DENY, + }; + } + /* highlight-add-end */ + + return { + result: AuthorizeResult.ALLOW, + }; +} +``` + +Now the frontend should show an error whenever you try to create a new Todo item. + +Let's flip the result back to `ALLOW` before moving on. + +```ts +if (isPermission(request.permission, todoListCreatePermission)) { + return { + /* highlight-remove-next-line */ + result: AuthorizeResult.DENY, + /* highlight-add-next-line */ + result: AuthorizeResult.ALLOW, + }; +} +``` + +At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. + +First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: + +```ts title="plugins/todo-list-backend/src/service/router.test.ts" +import { getVoidLogger } from '@backstage/backend-common'; +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +/* highlight-add-next-line */ +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; + +/* highlight-add-start */ +const mockedAuthorize: jest.MockedFunction = + jest.fn(); +const mockedPermissionQuery: jest.MockedFunction< + PermissionEvaluator['authorizeConditional'] +> = jest.fn(); + +const permissionEvaluator: PermissionEvaluator = { + authorize: mockedAuthorize, + authorizeConditional: mockedPermissionQuery, +}; +/* highlight-add-end */ + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + identity: {} as DefaultIdentityClient, + /* highlight-add-next-line */ + permissions: permissionEvaluator, + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); +}); +``` + +Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`: + +```ts title="plugins/todo-list-backend/src/service/standaloneServer.ts" +import { + createServiceBuilder, + loadBackendConfig, + SingleHostDiscovery, + /* highlight-add-next-line */ + ServerTokenManager, +} from '@backstage/backend-common'; +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +/* highlight-add-next-line */ +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'todo-list-backend' }); + logger.debug('Starting application server...'); + const config = await loadBackendConfig({ logger, argv: process.argv }); + const discovery = SingleHostDiscovery.fromConfig(config); + /* highlight-add-start */ + const tokenManager = ServerTokenManager.fromConfig(config, { + logger, + }); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); + /* highlight-add-end */ + const router = await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), + /* highlight-add-next-line */ + permissions, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/todo-list', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); +``` + +Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`: + +```ts title="plugins/todo-list-backend/src/plugin.ts" +import { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { createRouter } from './service/router'; + +/** +* The example TODO list backend plugin. +* +* @public +*/ +export const exampleTodoListPlugin = createBackendPlugin({ + pluginId: 'exampleTodoList', + register(env) { + env.registerInit({ + deps: { + identity: coreServices.identity, + logger: coreServices.logger, + httpRouter: coreServices.httpRouter, + /* highlight-add-next-line */ + permissions: coreServices.permissions, + }, + /* highlight-remove-next-line */ + async init({ identity, logger, httpRouter }) { + /* highlight-add-next-line */ + async init({ identity, logger, httpRouter, permissions }) { + httpRouter.use( + await createRouter({ + identity, + logger: loggerToWinstonLogger(logger), + permissions, + }), + ); + }, + }); + }, +}); +``` + +Now when you run `yarn tsc` you should have no more errors. 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 724edb7ccb..2d39d5cef3 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,6 +4,10 @@ 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](./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). @@ -57,30 +61,30 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: ```ts title="plugins/todo-list-backend/src/service/router.ts" /* highlight-remove-start */ import { InputError } from '@backstage/errors'; -import { IdentityApi } from '@backstage/plugin-auth-node'; +import { LoggerService, HttpAuthService } from '@backstage/backend-plugin-api'; /* highlight-remove-end */ /* highlight-add-start */ import { InputError, NotAllowedError } from '@backstage/errors'; -import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; -import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; +import { LoggerService, HttpAuthService, PermissionsService } from '@backstage/backend-plugin-api'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; /* highlight-add-end */ export interface RouterOptions { - logger: Logger; - identity: IdentityApi; + logger: LoggerService; + httpAuth: HttpAuthService; /* highlight-add-next-line */ - permissions: PermissionEvaluator; + permissions: PermissionsService; } export async function createRouter( options: RouterOptions, ): Promise { /* highlight-remove-next-line */ - const { logger, identity } = options; + const { logger, httpAuth } = options; /* highlight-add-next-line */ - const { logger, identity, permissions } = options; + const { logger, httpAuth, permissions } = options; /* highlight-add-start */ const permissionIntegrationRouter = createPermissionIntegrationRouter({ @@ -109,13 +113,12 @@ export async function createRouter( const user = await identity.getIdentity({ request: req }); author = user?.identity.userEntityRef; /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); const decision = ( - await permissions.authorize([{ permission: todoListCreatePermission }], { - token, - }) + await permissions.authorize( + [{ permission: todoListCreatePermission }], + { credentials }, + ) )[0]; if (decision.result === AuthorizeResult.DENY) { @@ -134,30 +137,43 @@ export async function createRouter( // ... ``` -Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`: +Pass the `permissions` object to the plugin in `plugins/todo-list-backend/src/plugin.ts`: -```ts title="packages/backend/src/plugins/todolist.ts" -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { createRouter } from '@internal/plugin-todo-list-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; +```ts title="plugins/todo-list-backend/src/plugin.ts" +import { coreServices, createBackendPlugin } from '@backstage/backend-plugin-api'; +import { createRouter } from './service/router'; -export default async function createPlugin({ - logger, - discovery, - /* highlight-add-next-line */ - permissions, -}: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); -} +export const exampleTodoListPlugin = createBackendPlugin({ + pluginId: 'todolist', + register(env) { + env.registerInit({ + deps: { + logger: coreServices.logger, + httpAuth: coreServices.httpAuth, + httpRouter: coreServices.httpRouter, + /* highlight-add-next-line */ + permissions: coreServices.permissions, + }, + /* highlight-remove-next-line */ + async init({ logger, httpAuth, httpRouter }) { + /* highlight-add-next-line */ + async init({ logger, httpAuth, httpRouter, permissions }) { + httpRouter.use( + await createRouter({ + logger, + httpAuth, + /* highlight-add-next-line */ + permissions, + }), + ); + httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + }, + }); + }, +}); ``` That's it! Now your plugin is fully configured. Let's try to test the logic by denying the permission. @@ -168,7 +184,15 @@ Before running this step, please make sure you followed the steps described in [ In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission: -```ts title="packages/backend/src/plugins/permission.ts" +```ts title="packages/backend/src/extensions/permissionsPolicyExtension.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + PolicyDecision, + /* highlight-add-start */ + isPermission, + /* highlight-add-end */ + AuthorizeResult, +} from '@backstage/plugin-permission-common'; import { PermissionPolicy, /* highlight-add-start */ @@ -177,9 +201,9 @@ import { /* highlight-add-end */ } from '@backstage/plugin-permission-node'; /* highlight-add-start */ -import { isPermission } from '@backstage/plugin-permission-common'; import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; /* highlight-add-end */ +import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha'; class TestPermissionPolicy implements PermissionPolicy { /* highlight-remove-next-line */ @@ -200,6 +224,19 @@ class TestPermissionPolicy implements PermissionPolicy { result: AuthorizeResult.ALLOW, }; } + +export default createBackendModule({ + pluginId: 'permission', + moduleId: 'permission-policy', + register(reg) { + reg.registerInit({ + deps: { policy: policyExtensionPoint }, + async init({ policy }) { + policy.setPolicy(new TestPermissionPolicy()); + }, + }); + }, +}); ``` Now the frontend should show an error whenever you try to create a new Todo item. @@ -217,42 +254,26 @@ if (isPermission(request.permission, todoListCreatePermission)) { } ``` -At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. +At this point everything is working but if you run `yarn tsc` you'll get an error, let's fix this up. -First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: +Clean up the `plugins/todo-list-backend/src/service/router.test.ts`: ```ts title="plugins/todo-list-backend/src/service/router.test.ts" -import { getVoidLogger } from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import express from 'express'; import request from 'supertest'; import { createRouter } from './router'; - -/* highlight-add-start */ -const mockedAuthorize: jest.MockedFunction = - jest.fn(); -const mockedPermissionQuery: jest.MockedFunction< - PermissionEvaluator['authorizeConditional'] -> = jest.fn(); - -const permissionEvaluator: PermissionEvaluator = { - authorize: mockedAuthorize, - authorizeConditional: mockedPermissionQuery, -}; -/* highlight-add-end */ +import { mockServices } from '@backstage/backend-test-utils'; describe('createRouter', () => { let app: express.Express; beforeAll(async () => { const router = await createRouter({ - logger: getVoidLogger(), - identity: {} as DefaultIdentityClient, + logger: mockServices.logger.mock(), + httpAuth: mockServices.httpAuth.mock(), /* highlight-add-next-line */ - permissions: permissionEvaluator, + permissions: mockServices.permissions.mock(), }); app = express().use(router); }); @@ -272,112 +293,4 @@ describe('createRouter', () => { }); ``` -Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`: - -```ts title="plugins/todo-list-backend/src/service/standaloneServer.ts" -import { - createServiceBuilder, - loadBackendConfig, - SingleHostDiscovery, - /* highlight-add-next-line */ - ServerTokenManager, -} from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'todo-list-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = SingleHostDiscovery.fromConfig(config); - /* highlight-add-start */ - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - /* highlight-add-end */ - const router = await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/todo-list', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); -``` - -Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`: - -```ts title="plugins/todo-list-backend/src/plugin.ts" -import { loggerToWinstonLogger } from '@backstage/backend-common'; -import { - coreServices, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; -import { createRouter } from './service/router'; - -/** -* The example TODO list backend plugin. -* -* @public -*/ -export const exampleTodoListPlugin = createBackendPlugin({ - pluginId: 'exampleTodoList', - register(env) { - env.registerInit({ - deps: { - identity: coreServices.identity, - logger: coreServices.logger, - httpRouter: coreServices.httpRouter, - /* highlight-add-next-line */ - permissions: coreServices.permissions, - }, - /* highlight-remove-next-line */ - async init({ identity, logger, httpRouter }) { - /* highlight-add-next-line */ - async init({ identity, logger, httpRouter, permissions }) { - httpRouter.use( - await createRouter({ - identity, - logger: loggerToWinstonLogger(logger), - permissions, - }), - ); - }, - }); - }, -}); -``` - Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md new file mode 100644 index 0000000000..ee06629e71 --- /dev/null +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md @@ -0,0 +1,299 @@ +--- +id: 03-adding-a-resource-permission-check--old +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 old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./03-adding-a-resource-permission-check.md) instead. Otherwise, [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 + +Let's add a new permission to the file `plugins/todo-list-common/src/permissions.ts` from [the previous section](./02-adding-a-basic-permission-check.md). + +```ts title="plugins/todo-list-common/src/permissions.ts" +import { createPermission } from '@backstage/plugin-permission-common'; + +/* highlight-add-next-line */ +export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; + +export const todoListCreatePermission = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, +}); + +/* highlight-add-start */ +export const todoListUpdatePermission = createPermission({ + name: 'todo.list.update', + attributes: { action: 'update' }, + resourceType: TODO_LIST_RESOURCE_TYPE, +}); +/* highlight-add-end */ + +/* highlight-remove-next-line */ +export const todoListPermissions = [todoListCreatePermission]; +/* highlight-add-start */ +export const todoListPermissions = [ + todoListCreatePermission, + todoListUpdatePermission, +]; +/* highlight-add-end */ +``` + +Notice that unlike `todoListCreatePermission`, the `todoListUpdatePermission` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource. + +## Setting up authorization for the update permission + +To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the same manner as we did in the previous section: + +```ts title="plugins/todo-list-backend/src/service/router.ts" +/* highlight-remove-next-line */ +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-start */ +import { + todoListCreatePermission, + todoListUpdatePermission, +} from '@internal/plugin-todo-list-common'; +/* highlight-add-end */ + +// ... + +const permissionIntegrationRouter = createPermissionIntegrationRouter({ + /* highlight-remove-next-line */ + permissions: [todoListCreatePermission], + /* highlight-add-next-line */ + permissions: [todoListCreatePermission, todoListUpdatePermission], +}); + +// ... + +router.put('/todos', async (req, res) => { + /* highlight-add-start */ + const token = getBearerTokenFromAuthorizationHeader( + req.header('authorization'), + ); + /* highlight-add-end */ + + if (!isTodoUpdateRequest(req.body)) { + throw new InputError('Invalid payload'); + } + /* highlight-add-start */ + const decision = ( + await permissions.authorize( + [{ permission: todoListUpdatePermission, resourceRef: req.body.id }], + { + token, + }, + ) + )[0]; + + if (decision.result !== AuthorizeResult.ALLOW) { + throw new NotAllowedError('Unauthorized'); + } + /* highlight-add-end */ + + res.json(update(req.body)); +}); +``` + +**Important:** Notice that we are passing an extra `resourceRef` field, with the `id` of the todo item as the value. + +This enables decisions based on characteristics of the resource, but it's important to note that policy authors will not have access to the resource ref inside of their permission policies. Instead, the policies will return conditional decisions, which we need to now support in our plugin. + +## Adding support for conditional decisions + +Install the missing module: + +```bash +$ yarn workspace @internal/plugin-todo-list-backend add zod +``` + +Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: + +```typescript title="plugins/todo-list-backend/src/service/rules.ts" +import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { z } from 'zod'; +import { Todo, TodoFilter } from './todos'; + +export const createTodoListPermissionRule = makeCreatePermissionRule< + Todo, + TodoFilter, + typeof TODO_LIST_RESOURCE_TYPE +>(); + +export const isOwner = createTodoListPermissionRule({ + name: 'IS_OWNER', + description: 'Should allow only if the todo belongs to the user', + resourceType: TODO_LIST_RESOURCE_TYPE, + paramsSchema: z.object({ + userId: z.string().describe('User ID to match on the resource'), + }), + apply: (resource: Todo, { userId }) => { + return resource.author === userId; + }, + toQuery: ({ userId }) => { + return { + property: 'author', + values: [userId], + }; + }, +}); + +export const rules = { isOwner }; +``` + +`makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query. + +:::note Note + +To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. + +::: + +We have created a new `isOwner` rule, which is going to be automatically used by the permission framework whenever a conditional response is returned in response to an authorized request with an attached `resourceRef`. +Specifically, the `apply` function is used to understand whether the passed resource should be authorized or not. + +Let's skip the `toQuery` function for now, we'll come back to that in the next section. + +Now, let's create the new endpoint by editing `plugins/todo-list-backend/src/service/router.ts`. This uses the `createPermissionIntegrationRouter` helper to add the APIs needed by the permission framework to your plugin. You'll need to supply: + +- `getResources`: a function that accepts an array of `resourceRefs` in the same format you expect to be passed to `authorize`, and returns an array of the corresponding resources. +- `resourceType`: the same value used in the permission rule above. +- `permissions`: the list of permissions that your plugin accepts. +- `rules`: an array of all the permission rules you want to support in conditional decisions. + +```ts title="plugins/todo-list-backend/src/service/router.ts" +// ... +import { + /* highlight-add-next-line */ + TODO_LIST_RESOURCE_TYPE, + todoListCreatePermission, + todoListUpdatePermission, +} from '@internal/plugin-todo-list-common'; +/* highlight-remove-next-line */ +import { add, getAll, update } from './todos'; +/* highlight-add-start */ +import { add, getAll, getTodo, update } from './todos'; +import { rules } from './rules'; +/* highlight-add-end */ + +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger, identity, permissions } = options; + + const permissionIntegrationRouter = createPermissionIntegrationRouter({ + permissions: [todoListCreatePermission, todoListUpdatePermission], + /* highlight-add-start */ + getResources: async resourceRefs => { + return resourceRefs.map(getTodo); + }, + resourceType: TODO_LIST_RESOURCE_TYPE, + rules: Object.values(rules), + /* highlight-add-end */ + }); + + const router = Router(); + router.use(express.json()); + + // ... +} +``` + +## Provide utilities for policy authors + +Now that we have a new resource type and a corresponding rule, we need to export some utilities for policy authors to reference them. + +Create a new `plugins/todo-list-backend/src/conditionExports.ts` file and add the following code: + +```typescript title="plugins/todo-list-backend/src/conditionExports.ts" +import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { createConditionExports } from '@backstage/plugin-permission-node'; +import { rules } from './service/rules'; + +const { conditions, createConditionalDecision } = createConditionExports({ + pluginId: 'todolist', + resourceType: TODO_LIST_RESOURCE_TYPE, + rules, +}); + +export const todoListConditions = conditions; + +export const createTodoListConditionalDecision = createConditionalDecision; +``` + +Make sure `todoListConditions` and `createTodoListConditionalDecision` are exported from the `todo-list-backend` package by editing `plugins/todo-list-backend/src/index.ts`: + +```ts title="plugins/todo-list-backend/src/index.ts" +export * from './service/router'; +/* highlight-add-next-line */ +export * from './conditionExports'; +export { exampleTodoListPlugin } from './plugin'; +``` + +## Test the authorized update endpoint + +Let's go back to the permission policy's handle function and try to authorize our new permission with an `isOwner` condition. + +```ts title="packages/backend/src/plugins/permission.ts" +import { + IdentityClient +} from '@backstage/plugin-auth-node'; +import { + PermissionPolicy, + PolicyQuery, + PolicyQueryUser, +} from '@backstage/plugin-permission-node'; +import { isPermission } from '@backstage/plugin-permission-common'; +/* highlight-remove-next-line */ +import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; +/* highlight-add-start */ +import { + todoListCreatePermission, + todoListUpdatePermission, +} from '@internal/plugin-todo-list-common'; +import { + todoListConditions, + createTodoListConditionalDecision, +} from '@internal/plugin-todo-list-backend'; +/* highlight-add-end */ + + +async handle( + request: PolicyQuery, + /* highlight-remove-next-line */ + _user?: PolicyQueryUser, + /* highlight-add-next-line */ + user?: PolicyQueryUser, +): Promise { + if (isPermission(request.permission, todoListCreatePermission)) { + return { + result: AuthorizeResult.ALLOW, + }; + } + /* highlight-add-start */ + if (isPermission(request.permission, todoListUpdatePermission)) { + return createTodoListConditionalDecision( + request.permission, + todoListConditions.isOwner({ + userId: user?.info.userEntityRef ?? '', + }), + ); + } + /* highlight-add-end */ + + return { + result: AuthorizeResult.ALLOW, + }; +} +``` + +For any incoming update requests, we now return a _Conditional Decision_. We are saying: + +> Hey permission framework, I can't make a decision alone. Please go to the plugin with id `todolist` and ask it to apply these conditions. + +To check that everything works as expected, you should now see an error in the UI whenever you try to edit an item that wasn’t created by you. Success! 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 7ad551e926..3f5a9777b3 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,6 +4,10 @@ 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](./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 @@ -68,9 +72,7 @@ const permissionIntegrationRouter = createPermissionIntegrationRouter({ router.put('/todos', async (req, res) => { /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); /* highlight-add-end */ if (!isTodoUpdateRequest(req.body)) { @@ -80,9 +82,7 @@ router.put('/todos', async (req, res) => { const decision = ( await permissions.authorize( [{ permission: todoListUpdatePermission, resourceRef: req.body.id }], - { - token, - }, + { credentials }, ) )[0]; @@ -146,7 +146,7 @@ export const rules = { isOwner }; :::note Note -To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. +To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `extension point`. ::: diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md new file mode 100644 index 0000000000..cca6899ef6 --- /dev/null +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md @@ -0,0 +1,193 @@ +--- +id: 04-authorizing-access-to-paginated-data--old +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 old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./04-authorizing-access-to-paginated-data.md) instead. Otherwise, [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`: + +```ts +router.get('/todos', async (req, res) => { + /* highlight-add-next-line */ + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); + + /* highlight-remove-next-line */ + res.json(getAll()); + /* highlight-add-start */ + const items = getAll(); + const decisions = await permissions.authorize( + items.map(({ id }) => ({ + permission: todoListReadPermission, + resourceRef: id, + })), + { credentials }, + ); + + const filteredItems = decisions.filter( + decision => decision.result === AuthorizeResult.ALLOW, + ); + res.json(filteredItems); + /* highlight-add-end */ +}); +``` + +This approach will work for simple cases, but it has a downside: it forces us to retrieve all the elements upfront and authorize them one by one. This forces the plugin implementation to handle concerns like pagination, which is currently handled by the data source. + +To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. + +:::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. + +::: + +## Creating the read permission + +Let's add another permission to the plugin. + +```ts title="plugins/todo-list-backend/src/service/permissions.ts" +import { createPermission } from '@backstage/plugin-permission-common'; + +export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; + +export const todoListCreatePermission = createPermission({ + name: 'todo.list.create', + attributes: { action: 'create' }, +}); + +export const todoListUpdatePermission = createPermission({ + name: 'todo.list.update', + attributes: { action: 'update' }, + resourceType: TODO_LIST_RESOURCE_TYPE, +}); + +/* highlight-add-start */ +export const todoListReadPermission = createPermission({ + name: 'todos.list.read', + attributes: { action: 'read' }, + resourceType: TODO_LIST_RESOURCE_TYPE, +}); +/* highlight-add-end */ + +export const todoListPermissions = [ + todoListCreatePermission, + todoListUpdatePermission, + /* highlight-add-start */ + todoListReadPermission, + /* highlight-add-end */ +]; +``` + +## Using conditional policy decisions + +So far we've only used the `PermissionsService.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionsService.authorizeConditional` instead. + +```ts title="plugins/todo-list-backend/src/service/router.ts" +/* highlight-remove-next-line */ +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +/* highlight-add-start */ +import { + createPermissionIntegrationRouter, + createConditionTransformer, + ConditionTransformer, +} from '@backstage/plugin-permission-node'; +/* highlight-add-end */ +/* highlight-remove-next-line */ +import { add, getAll, getTodo, update } from './todos'; +/* highlight-add-next-line */ +import { add, getAll, getTodo, TodoFilter, update } from './todos'; +import { + TODO_LIST_RESOURCE_TYPE, + todoListCreatePermission, + todoListUpdatePermission, + /* highlight-add-next-line */ + todoListReadPermission, +} from './permissions'; + +// ... + +const permissionIntegrationRouter = createPermissionIntegrationRouter({ + /* highlight-remove-next-line */ + permissions: [todoListCreatePermission, todoListUpdatePermission], + /* highlight-add-next-line */ + permissions: [todoListCreatePermission, todoListUpdatePermission, todoListReadPermission], + getResources: async resourceRefs => { + return resourceRefs.map(getTodo); + }, + resourceType: TODO_LIST_RESOURCE_TYPE, + rules: Object.values(rules), +}); + +// ... + +/* highlight-add-next-line */ +const transformConditions: ConditionTransformer = createConditionTransformer(Object.values(rules)); + +/* highlight-remove-next-line */ +router.get('/todos', async (_req, res) => { +/* highlight-add-start */ +router.get('/todos', async (req, res) => { + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); + + const decision = ( + await permissions.authorizeConditional([{ permission: todoListReadPermission }], { + credentials, + }) + )[0]; + + if (decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + + if (decision.result === AuthorizeResult.CONDITIONAL) { + const filter = transformConditions(decision.conditions); + res.json(getAll(filter)); + } else { + res.json(getAll()); + } +/* highlight-add-end */ + /* highlight-remove-next-line */ + res.json(getAll()); +}); +``` + +To make the process of handling conditional decisions easier, the permission framework provides a `createConditionTransformer` helper. This function accepts an array of permission rules, and returns a transformer function which converts the conditions to the format needed by the plugin using the `toQuery` method defined on each rule. + +Since `TodoFilter` used in our plugin matches the structure of the conditions object, we can directly pass the output of our condition transformer. If the filters were structured differently, we'd need to transform it further before passing it to the api. + +## Test the authorized read endpoint + +Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission. + +```ts title="packages/backend/src/plugins/permission.ts" +import { + todoListCreatePermission, + todoListUpdatePermission, + /* highlight-add-next-line */ + todoListReadPermission, +} from '@internal/plugin-todo-list-common'; + +/* highlight-remove-next-line */ +if (isPermission(request.permission, todoListUpdatePermission)) { +/* highlight-add-start */ +if ( + isPermission(request.permission, todoListUpdatePermission) || + isPermission(request.permission, todoListReadPermission) +) { +/* highlight-add-end */ + return createTodoListConditionalDecision( + request.permission, + todoListConditions.isOwner({ + userId: user?.identity.userEntityRef + }), + ); +} +``` + +Once the changes to the permission policy are saved, the UI should show only the todo items you've created. 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 84e86140fc..883884b6e5 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,6 +4,8 @@ title: 4. Authorizing access to paginated data description: Explains how to authorize access to paginated data in a Backstage plugin --- +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](./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`: @@ -11,7 +13,7 @@ One possible solution may leverage the batching functionality to authorize all o ```ts router.get('/todos', async (req, res) => { /* highlight-add-next-line */ - const token = IdentityClient.getBearerToken(req.header('authorization')); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); /* highlight-remove-next-line */ res.json(getAll()); @@ -22,6 +24,7 @@ router.get('/todos', async (req, res) => { permission: todoListReadPermission, resourceRef: id, })), + { credentials }, ); const filteredItems = decisions.filter( @@ -81,7 +84,7 @@ export const todoListPermissions = [ ## Using conditional policy decisions -So far we've only used the `PermissionEvaluator.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionEvaluator.authorizeConditional` instead. +So far we've only used the `PermissionsService.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionsService.authorizeConditional` instead. ```ts title="plugins/todo-list-backend/src/service/router.ts" /* highlight-remove-next-line */ @@ -128,13 +131,11 @@ const transformConditions: ConditionTransformer = createConditionTra router.get('/todos', async (_req, res) => { /* highlight-add-start */ router.get('/todos', async (req, res) => { - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); const decision = ( await permissions.authorizeConditional([{ permission: todoListReadPermission }], { - token, + credentials, }) )[0]; diff --git a/docs/plugins/integrating-plugin-into-software-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md index 1ec3df69cf..d47be7f4c0 100644 --- a/docs/plugins/integrating-plugin-into-software-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -54,7 +54,7 @@ To begin, you will need to import your plugin in the entities page. Located at your backstage app. ```tsx -import { MyPluginEntityContent } from '@backstage/plugin-my-plugin; +import { MyPluginEntityContent } from '@backstage/plugin-my-plugin'; ``` To add your component to the Entity view, you will need to modify the diff --git a/docs/plugins/integrating-search-into-plugins--old.md b/docs/plugins/integrating-search-into-plugins--old.md new file mode 100644 index 0000000000..2729dd3d21 --- /dev/null +++ b/docs/plugins/integrating-search-into-plugins--old.md @@ -0,0 +1,421 @@ +--- +id: integrating-search-into-plugins--old +title: Integrating Search into a plugin +description: How to integrate Search into a Backstage plugin +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./integrating-search-into-plugins.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +The Backstage Search Platform was designed to give plugin developers the APIs +and interfaces needed to offer search experiences within their plugins, while +abstracting away (and instead empowering application integrators to choose) the +specific underlying search technologies. + +On this page, you'll find concepts and tutorials for leveraging the Backstage +Search Platform in your plugin. + +## Providing data to the search platform + +### Create a collator + +> Knowing what a [collator](../features/search/concepts.md#collators) is will help you as you build it out. + +Imagine you have a plugin that is responsible for storing FAQ snippets in a database. You want other engineers to be able to easily find your questions and answers. So that means you want them to be indexed by the search platform. Lets say the FAQ snippets can be viewed at a URL like `backstage.example.biz/faq-snippets`. + +The search platform provides an interface (`DocumentCollatorFactory` from package `@backstage/plugin-search-common`) that allows you to do exactly that. It works by registering each of your entries as a "document" that later represents one search result each. + +> You can always look at a working example, e.g. [StackOverflowQuestionsCollatorFactory](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts), if you are unsure or want to follow best practices. + +#### 1. Install collator interface dependencies + +We will need the interface `DocumentCollatorFactory` from package `@backstage/plugin-search-common`, so let's add it to your plugins dependencies: + +```sh +# navigate to the plugin directory +# (for this tutorial our plugin lives in the backstage repo, if your plugin lives in a separate repo you need to clone that first) +cd plugins/faq-snippets + +# Create a new branch using Git command-line +git checkout -b tutorials/new-faq-snippets-collator + +# Install the package containing the interface +yarn add @backstage/plugin-search-common +``` + +#### 2. Define your document type + +Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. + +Create a new file `plugins/faq-snippets/src/search/collators/FaqSnippetDocument.ts` and paste the following below: + +```ts +import { IndexableDocument } from '@backstage/plugin-search-common'; + +export interface FaqSnippetDocument extends IndexableDocument { + answered_by: string; +} +``` + +#### 3. Use Backstage App configuration + +Your new collator could benefit from using configuration directly from the Backstage `app-config.yaml` file which is located on the project's root folder: + +```yaml +faq: + baseUrl: https://backstage.example.biz/faq-snippets +``` + +#### 4. Implement your collator + +Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq-snippets` with following JSON response format: + +```json +{ + "items": [ + { + "id": 42, + "question": "What is The Answer to the Ultimate Question of Life, the Universe, and Everything?", + "answer": "Forty-two", + "user": "Deep Thought" + } + ] +} +``` + +Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/faq-snippets/src/search/collators/FaqCollatorFactory.ts` file: + +```ts +import fetch from 'cross-fetch'; +import { Logger } from 'winston'; +import { Config } from '@backstage/config'; +import { Readable } from 'stream'; +import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; + +import { FaqDocument } from './FaqDocument'; + +export type FaqCollatorFactoryOptions = { + baseUrl?: string; + logger: Logger; +}; + +export class FaqCollatorFactory implements DocumentCollatorFactory { + private readonly baseUrl: string | undefined; + private readonly logger: Logger; + public readonly type: string = 'faq-snippets'; + + private constructor(options: FaqCollatorFactoryOptions) { + this.baseUrl = options.baseUrl; + this.logger = options.logger; + } + + static fromConfig(config: Config, options: FaqCollatorFactoryOptions) { + const baseUrl = + config.getOptionalString('faq.baseUrl') || + 'https://backstage.example.biz/faq-snippets'; + return new FaqCollatorFactory({ ...options, baseUrl }); + } + + async getCollator() { + return Readable.from(this.execute()); + } + + async *execute(): AsyncGenerator { + if (!this.baseUrl) { + this.logger.error(`No faq.baseUrl configured in your app-config.yaml`); + return; + } + + const response = await fetch(this.baseUrl); + const data = await response.json(); + + for (const faq of data.items) { + yield { + title: faq.question, + location: `/faq-snippets/${faq.id}`, + text: faq.answer, + answered_by: faq.user, + }; + } + } +} +``` + +#### 5. Test your 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/docs/reference/plugin-search-backend-node.testpipeline) 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. + +#### 6. Make your plugins collator discoverable for others + +If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/#plugins-integrated-with-backstage-search). + +## Building a search experience into your plugin + +While the core Search plugin offers components and extensions that empower app +integrators to compose a global search experience, you may find that you want a +narrower search experience just within your plugin. This could be as literal as +an autocomplete-style search bar focused on documents provided by your plugin +(for example, the [TechDocsSearch](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/search/components/TechDocsSearch.tsx) +component), or as abstract as a widget that presents a list of links that +are contextually related to something else on the page. + +### Search Experience Concepts + +Knowing these high-level concepts will help you as you craft your in-plugin +search experience. + +- All search experiences must be wrapped in a ``, which + is provided by `@backstage/plugin-search-react`. This context keeps track + 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) + for details. +- The aforementioned state can be modified and/or consumed via the + `useSearch()` hook, also exported by `@backstage/plugin-search-react`. +- For more literal search experiences, reusable components are available + to import and compose into a cohesive experience in your plugin (e.g. + `` or ``). You can see all such + components in [Backstage's storybook](https://backstage.io/storybook/?path=/story/plugins-search-searchbar--default). + +### Search Experience Tutorials + +The following tutorials make use of packages and plugins that you may not yet +have as dependencies for your plugin; be sure to add them before you use them! + +- [`@backstage/plugin-search-react`](https://www.npmjs.com/package/@backstage/plugin-search-react) - A + package containing components, hooks, and types that are shared across all + frontend plugins, including plugins like yours! +- [`@backstage/plugin-search`](https://www.npmjs.com/package/@backstage/plugin-search) - The + main search plugin, used by app integrators to compose global search + experiences. +- [`@backstage/core-components`](https://www.npmjs.com/package/@backstage/core-components) - A + package containing generic components useful for a variety of experiences + built in Backstage. + +#### Improved "404" page experience + +Imagine you have a plugin that allows users to manage _widgets_. Perhaps they +can be viewed at a URL like `backstage.example.biz/widgets/{widgetName}`. +At some point, a widget is renamed, and links to that widget's page from +chat systems, wikis, or browser bookmarks become stale, resulting in errors or +404s. + +What if instead of showing a broken page or the generic "looks like someone +dropped the mic" 404 page, you showed a list of possibly related widgets? + +```javascript +import { Link } from '@backstage/core-components'; +import { SearchResult } from '@backstage/plugin-search'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; + +export const Widget404Page = ({ widgetName }) => { + // Supplying this to runs a pre-filtered search with + // the given widgetName as the search term, focused on search result of type + // "widget" with no other filters. + const preFiltered = { + term: widgetName, + types: ['widget'], + filters: {}, + }; + + return ( + + {/* The component allows us to iterate through results and + display them in whatever way fits best! */} + + {({ results }) => ( + {results.map(({ document }) => ( + + {document.title} + + ))} + )} + + + ); +); +``` + +Not all search experiences require user input! As you can see, it's possible to +leverage the Backstage Search Platform's frontend framework without necessarily +giving users input controls. + +#### Simple search page + +Of course, it's also possible to provide a more fully featured search +experience in your plugin. The simplest way is to leverage reusable components +provided by the `@backstage/plugin-search` package, like this: + +```javascript +import { useProfile } from '@internal/api'; +import { + Content, + ContentHeader, + PageWithHeader, +} from '@backstage/core-components'; +import { SearchBar, SearchResult } from '@backstage/plugin-search'; +import { SearchContextProvider } from '@backstage/plugin-search-react'; + +export const ManageMyWidgets = () => { + const { primaryTeam } = useProfile(); + // In this example, note how we are pre-filtering results down to a specific + // owner field value (the currently logged-in user's team), but allowing the + // search term to be controlled by the user via the component. + const preFiltered = { + types: ['widget'], + term: '', + filters: { + owner: primaryTeam, + }, + }; + + return ( + + + + + + + {/* Render results here, just like above */} + + + + + ); +}; +``` + +#### Custom search control surfaces + +If the reusable search components provided by `@backstage/plugin-search` aren't +adequate, no problem! There's an API in place that you can use to author your +own components to control the various parts of the search context. + +```javascript +import { useSearch } from '@backstage/plugin-search-react'; +import ChipInput from 'material-ui-chip-input'; + +export const CustomChipFilter = ({ name }) => { + const { filters, setFilters } = useSearch(); + const chipValues = filters[name] || []; + + // When a chip value is changed, update the filters value by calling the + // setFilters function from the search context. + const handleChipChange = (chip, index) => { + // There may be filters set for other fields. Be sure to maintain them. + setFilters(prevState => { + const { [name]: filter = [], ...others } = prevState; + + if (index === undefined) { + filter.push(chip); + } else { + filter.splice(index, 1); + } + + return { ...others, [name]: filter }; + }); + }; + + return ( + + ); +}; +``` + +Check out the [SearchContextValue type](https://github.com/backstage/backstage/blob/master/plugins/search-react/src/context/SearchContext.tsx) +for more details on what methods and values are available for manipulating and +reading the search context. + +If you produce something generic and reusable, consider contributing your +component upstream so that all users of the Backstage Search Platform can +benefit. Issues and pull requests welcome. + +#### Custom search results + +Search results throughout Backstage are rendered as lists so that list items can easily be customized; although a [default result list item](https://backstage.io/storybook/?path=/story/plugins-search-defaultresultlistitem--default) is available, plugins are in the best position to provide custom result list items that surface relevant information only known to the plugin. + +The example below imagines `YourCustomSearchResult` as a type of search result that contains associated `tags` which could be rendered as chips below the title/text. + +```tsx +import { Link } from '@backstage/core-components'; +import { useAnalytics } from '@backstage/core-plugin-api'; +import { ResultHighlight } from '@backstage/plugin-search-common'; +import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; + +type CustomSearchResultListItemProps = { + result: YourCustomSearchResult; + rank?: number; + highlight?: ResultHighlight; +}; + +export const CustomSearchResultListItem = ( + props: CustomSearchResultListItemProps, +) => { + const { title, text, location, tags } = props.result; + + const analytics = useAnalytics(); + const handleClick = () => { + analytics.captureEvent('discover', title, { + attributes: { to: location }, + value: props.rank, + }); + }; + + return ( + + + + + ) : ( + title + ) + } + secondary={ + highlight?.fields?.text ? ( + + ) : ( + text + ) + } + /> + {tags && + tags.map((tag: string) => ( + + ))} + + + + + ); +}; +``` + +The optional use of the `` component makes it possible to highlight relevant parts of the result based on the user's search query. + +**Note on Analytics**: In order for app integrators to track and improve search experiences across Backstage, it's important for them to understand when and what users search for, as well as what they click on after searching. When providing a custom result component, it's your responsibility as a plugin developer to instrument it according to search analytics conventions. In particular: + +- You must use the `analytics.captureEvent` method, from the `useAnalytics()` hook (detailed [plugin analytics docs are here](./analytics.md)). +- You must ensure that the action of the event, representing a click on a search result item, is `discover`, and the subject is the `title` of the clicked result. In addition, the `to` attribute should be set to the result's `location`, and the `value` of the event must be set to the `rank` (passed in as a prop). +- You must ensure that the aforementioned `captureEvent` method is called when a user clicks the link; you should further ensure that the `noTrack` prop is added to the link (which disables default link click tracking, in favor of this custom instrumentation). + +For other examples and inspiration on custom result list items, check out the [``](https://github.com/backstage/backstage/blob/c981e83/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx) or [``](https://github.com/backstage/backstage/blob/c981e83/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx) components. diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md index 28c8b410e3..62b3942537 100644 --- a/docs/plugins/integrating-search-into-plugins.md +++ b/docs/plugins/integrating-search-into-plugins.md @@ -4,6 +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](./integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + The Backstage Search Platform was designed to give plugin developers the APIs and interfaces needed to offer search experiences within their plugins, while abstracting away (and instead empowering application integrators to choose) the @@ -24,34 +28,25 @@ The search platform provides an interface (`DocumentCollatorFactory` from packag > You can always look at a working example, e.g. [StackOverflowQuestionsCollatorFactory](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts), if you are unsure or want to follow best practices. -#### 1. Install collator interface dependencies +#### 1. Create a collator module package -We will need the interface `DocumentCollatorFactory` from package `@backstage/plugin-search-common`, so let's add it to your plugins dependencies: +In order to add an FAQ collator to the Backstage index registry we have to create a [plugin module](https://backstage.io/docs/backend-system/building-plugins-and-modules/index#modules), and the best way to do this is to create it in a separate package, e.g., `plugins/search-backend-module-faq-snippets-collator`, using the `yarn new` command: + +1. Access your Backstage project root directory and run `yarn new`; +2. When asked about what do you want to create, please select `backend-module` and hit enter; +3. Input `search` as the plugin ID and `faq-snippets-collator` as the module ID; +4. A `search-backend-module-faq-snippets-collator` folder should have been created in your project's "plugins" directory. + +#### 2. Install the collator dependencies + +We will use some libraries in the module, so let's add them to your plugin module dependencies: ```sh -# navigate to the plugin directory -# (for this tutorial our plugin lives in the backstage repo, if your plugin lives in a separate repo you need to clone that first) -cd plugins/faq-snippets - # Create a new branch using Git command-line git checkout -b tutorials/new-faq-snippets-collator # Install the package containing the interface -yarn add @backstage/plugin-search-common -``` - -#### 2. Define your document type - -Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. - -Create a new file `plugins/faq-snippets/src/search/collators/FaqSnippetDocument.ts` and paste the following below: - -```ts -import { IndexableDocument } from '@backstage/plugin-search-common'; - -export interface FaqSnippetDocument extends IndexableDocument { - answered_by: string; -} +yarn workspace @internal/backstage-plugin-search-backend-module-faq-snippets-collator add node-fetch @backstage/plugin-search-common @backstage/plugin-search-backend-node ``` #### 3. Use Backstage App configuration @@ -63,7 +58,35 @@ faq: baseUrl: https://backstage.example.biz/faq-snippets ``` -#### 4. Implement your collator +It is optional to define a schedule for the collator to run, or else it defaults to the value in the collator factory code (See [5. Implement the collator factory](#5-implement-the-collator-factory)): + +```yaml +faq: + baseUrl: https://backstage.example.biz/faq-snippets + /* highlight-add-start */ + schedule: + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } + /* highlight-add-end */ +``` + +#### 4. Define the collator document type + +Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. + +Create a new file `plugins/search-backend-module-faq-snippets-collator/src/types.ts` and paste the following below: + +```ts +import { IndexableDocument } from '@backstage/plugin-search-common'; + +export interface FaqSnippetDocument extends IndexableDocument { + answered_by: string; +} +``` + +#### 5. Implement the collator factory Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq-snippets` with following JSON response format: @@ -80,52 +103,50 @@ Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq } ``` -Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/faq-snippets/src/search/collators/FaqCollatorFactory.ts` file: +Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/search-backend-module-faq-snippets-collator/src/factory.ts` file: ```ts -import fetch from 'cross-fetch'; -import { Logger } from 'winston'; -import { Config } from '@backstage/config'; +import fetch from 'node-fetch'; import { Readable } from 'stream'; + +import { + LoggerService, + RootConfigService, +} from '@backstage/backend-plugin-api'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; -import { FaqDocument } from './FaqDocument'; +import { FaqSnippetDocument } from './types'; -export type FaqCollatorFactoryOptions = { - baseUrl?: string; - logger: Logger; -}; +const DEFAULT_BASE_URL = 'https://backstage.example.biz/faq-snippets'; -export class FaqCollatorFactory implements DocumentCollatorFactory { - private readonly baseUrl: string | undefined; - private readonly logger: Logger; +export class FaqSnippetsCollatorFactory implements DocumentCollatorFactory { public readonly type: string = 'faq-snippets'; + private readonly baseUrl: string; + private readonly logger: LoggerService; - private constructor(options: FaqCollatorFactoryOptions) { + private constructor(options: { logger: LoggerService; baseUrl: string }) { this.baseUrl = options.baseUrl; this.logger = options.logger; } - static fromConfig(config: Config, options: FaqCollatorFactoryOptions) { - const baseUrl = - config.getOptionalString('faq.baseUrl') || - 'https://backstage.example.biz/faq-snippets'; - return new FaqCollatorFactory({ ...options, baseUrl }); + static fromConfig( + config: RootConfigService, + options: { + logger: LoggerService; + }, + ) { + const baseUrl = config.getOptionalString('faq.baseUrl') ?? DEFAULT_BASE_URL; + return new FaqSnippetsCollatorFactory({ ...options, baseUrl }); } async getCollator() { return Readable.from(this.execute()); } - async *execute(): AsyncGenerator { - if (!this.baseUrl) { - this.logger.error(`No faq.baseUrl configured in your app-config.yaml`); - return; - } - + async *execute(): AsyncGenerator { + this.logger.info(`Fetching faq snippets from ${this.baseUrl}`); const response = await fetch(this.baseUrl); const data = await response.json(); - for (const faq of data.items) { yield { title: faq.question, @@ -138,13 +159,109 @@ export class FaqCollatorFactory implements DocumentCollatorFactory { } ``` -#### 5. Test your collator +#### 6. Implement the collator plugin module + +Now we have to connect the search backend plugin with our FAQ Snippets collator factory, so replace the `module.ts` file with the content below: + +```ts title='plugins/search-backend-module-faq-snippets-collator/src/module.ts' +import { + coreServices, + createBackendModule, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; +import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha'; +import { FaqSnippetsCollatorFactory } from './factory'; + +export const searchFaqSnippetsCollatorModule = createBackendModule({ + pluginId: 'search', + moduleId: 'faq-snippets-collator', + register(env) { + env.registerInit({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + indexRegistry: searchIndexRegistryExtensionPoint, + }, + async init({ config, logger, scheduler, indexRegistry }) { + const defaultSchedule = { + frequency: { minutes: 10 }, + timeout: { minutes: 15 }, + initialDelay: { seconds: 3 }, + }; + + const schedule = config.has('faq.schedule') + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('faq.schedule'), + ) + : defaultSchedule; + + indexRegistry.addCollator({ + schedule: scheduler.createScheduledTaskRunner(schedule), + factory: FaqSnippetsCollatorFactory.fromConfig(config, { logger }), + }); + }, + }); + }, +}); +``` + +In the fragment above, the module is registered, and when the Backstage backend initializes it, it adds the FAQ Snippets collator to the search index registry. Now let's export the module as default from the `index.ts` file: + +```ts title='plugins/search-backend-module-faq-snippets-collator/src/index.ts' +export { searchFaqSnippetsCollatorModule as default } from './module'; +``` + +#### 7. Install the collator module + +The newly created module should be added to the backend package dependencies as follows: + +```sh +yarn --cwd packages/backend add @internal/backstage-plugin-search-backend-module-faq-snippets-collator +``` + +After that, install the module on your Backstage backend instance: + +```ts title='packages/backend/src/index.ts' +import { createBackend } from '@backstage/backend-defaults'; +//... +const backend = createBackend(); +// Installing the search backend plugin +backend.add(import('@backstage/plugin-search-backend/alpha')); +// Installing the newly created faq snippets collator module +backend.add( + import( + '@internal/backstage-plugin-search-backend-module-faq-snippets-collator' + ), +); +//... +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. Look at [DefaultTechDocsCollatorFactory test](https://github.com/backstage/backstage/blob/de294ce5c410c9eb56da6870a1fab795268f60e3/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts), for an example. -#### 6. Make your plugins collator discoverable for others +You can also check out the documentation on [how to test Backstage plugin modules](../backend-system/building-plugins-and-modules/02-testing.md). + +#### 9. Running the collator locally + +Run `yarn dev` in the root folder of your Backstage project and look for logs like these: + +```sh +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Task worker starting: search_index_faq_snippets, {"version":2,"cadence":"PT10M","initialDelayDuration":"PT3S","timeoutAfterDuration":"PT15M"} task=search_index_faq_snippets +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Collating documents for faq-snippets via FaqSnippetsCollatorFactory documentType=faq-snippets +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Fetching faq snippets from https://backstage.example.biz/faq-snippets +[backend]: YYYY-MM-DDTHH:MM:SS.000Z search info Collating documents for faq-snippets succeeded documentType=faq-snippets +``` + +It means that the collator task was started and completed successfully. Visit http://localhost:3000, log in, select the 'All' tab, and type in one of your snippets title in the search box. + +Results should appear for snippets. + +#### 10. Make your plugins collator discoverable for others If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/#plugins-integrated-with-backstage-search). diff --git a/docs/releases/v1.31.0-next.1-changelog.md b/docs/releases/v1.31.0-next.1-changelog.md new file mode 100644 index 0000000000..eb9b11125e --- /dev/null +++ b/docs/releases/v1.31.0-next.1-changelog.md @@ -0,0 +1,2176 @@ +# Release v1.31.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.31.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.31.0-next.1) + +## @backstage/backend-common@0.25.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 988c145: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + - The deprecated `dropDatabase` function has now been removed, without replacement. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.5.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 055b75b: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + For the `/database` subpath exports: + + - The deprecated `dropDatabase` function has now been removed, without replacement. + - The deprecated `LegacyRootDatabaseService` type has now been removed. + - The return type from `DatabaseManager.forPlugin` is now directly a `DatabaseService`, as arguably expected. + - `DatabaseManager.forPlugin` now requires the `deps` argument, with the logger and lifecycle services. + + For the `/cache` subpath exports: + + - The `PluginCacheManager` type has been removed. You can still import it from `@backstage/backend-common`, but it's deprecated there, and you should move off of that package by migrating fully to the new backend system. + - Accordingly, `CacheManager.forPlugin` immediately returns a `CacheService` instead of a `PluginCacheManager`. The outcome of this is that you no longer need to make the extra `.getClient()` call. The old `CacheManager` with the old behavior still exists on `@backstage/backend-common`, but the above recommendations apply. + +### Patch Changes + +- 622360e: Move down the discovery config to be in the root +- fe6fd8c: Accept `ConfigService` instead of `Config` in constructors/factories +- 5705424: Wrap scheduled tasks from the scheduler core service now in OpenTelemetry spans +- b2a329d: Properly indent the config schema +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/core-compat-api@0.3.0-next.1 + +### Minor Changes + +- 6db849e: **BREAKING**: The `namespace` parameter for API's is now defaulted to the `pluginId` which was discovered. This means that if you're overriding API's by using ID's directly, they might have changed to include the plugin ID too. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-app-api@0.9.0-next.1 + +### Minor Changes + +- 7c80650: **BREAKING**: The `createSpecializedApp` function now creates a bare-bones app without any of the default app structure or APIs. To re-introduce this functionality if you need to use `createSpecializedApp` you can install the `app` plugin from `@backstage/plugin-app`. + + In addition, the `createApp` and `CreateAppFeatureLoader` exports are now deprecated as they are being moved to `@backstage/frontend-defaults`, which should be used instead. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-defaults@0.1.0-next.0 + +### Minor Changes + +- 7c80650: Initial release of this package, which provides a default app setup through the `createApp` function. This replaces the existing `createApp` method from `@backstage/frontend-app-api`. + +### Patch Changes + +- 7d19cd5: Added a new `CreateAppOptions` type for the `createApp` options. +- 7d19cd5: Added `createPublicSignInApp`, used to creating apps for the public entry point. +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/app-defaults@1.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/backend-app-api@0.10.0-next.1 + +### Patch Changes + +- c246372: Updated the error message for missing service dependencies to include the plugin and module IDs. +- 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/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/backend-dynamic-feature-service@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/backend-openapi-utils@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/backend-test-utils@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 + +## @backstage/catalog-client@1.6.7-next.0 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/cli@0.27.1-next.1 + +### Patch Changes + +- d2d2313: Add `config.d.ts` files to the list of included file in `tsconfig.json`. + + This allows ESLint to detect issues or deprecations in those files. + +- 97422b0: Update templates to not refer to backend-common + +- f865103: Updated dependency `esbuild` to `^0.23.0`. + +- 569c3f0: Fixed an issue where published frontend packages would end up with an invalid import structure if a single module imported both `.css` and `.svg` files. + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/codemods@0.1.50-next.0 + +### Patch Changes + +- 0894166: Updated dependency `jscodeshift` to `^0.16.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/core-components@0.14.11-next.0 + +### Patch Changes + +- 06b8206: Added `titleComponent` prop to `SignInPage` component to allow further customization of the title using `ReactNode` +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.19-next.1 + +### Patch Changes + +- d2d2313: Add `config.d.ts` files to the list of included file in `tsconfig.json`. + + This allows ESLint to detect issues or deprecations in those files. + +- 97422b0: Update templates to not refer to backend-common + +- bf370c2: Remove references to the `@backstage/backend-tasks` in versions of the `create-app` package, as it has been deprecated. + +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/app-defaults@1.5.11-next.0 + - @backstage/catalog-model@1.6.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/frontend-plugin-api@0.8.0-next.1 + +### Patch Changes + +- c816e2d: Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated. + + Deprecated the `BackstagePlugin` and `FrontendFeature` type in favor of `FrontendPlugin` and `FrontendFeature` from `@backstage/frontend-app-api` respectively. + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-test-utils@0.2.0-next.1 + +### Patch Changes + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.9.7-next.1 + +### Patch Changes + +- 5c4aa2f: Updated dependency `@useoptic/openapi-utilities` to `^0.55.0`. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## @backstage/plugin-api-docs@0.11.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-app@0.1.0-next.1 + +### Patch Changes + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-app-backend@0.3.74-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/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + +## @backstage/plugin-app-node@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config-loader@1.9.0 + +## @backstage/plugin-app-visualizer@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.23.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.1 + - @backstage/plugin-auth-node@0.5.2-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 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.1 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@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/plugin-auth-backend-module-aws-alb-provider@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 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@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 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@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/plugin-auth-backend-module-cloudflare-access-provider@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 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@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 + +## @backstage/plugin-auth-backend-module-github-provider@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/plugin-auth-backend-module-gitlab-provider@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/plugin-auth-backend-module-google-provider@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/plugin-auth-backend-module-guest-provider@0.2.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/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@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/plugin-auth-backend-module-oauth2-provider@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/plugin-auth-backend-module-oauth2-proxy-provider@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 + +## @backstage/plugin-auth-backend-module-oidc-provider@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 + +## @backstage/plugin-auth-backend-module-okta-provider@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 + +## @backstage/plugin-auth-backend-module-onelogin-provider@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/plugin-auth-backend-module-pinniped-provider@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/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@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/catalog-model@1.6.0 + +## @backstage/plugin-auth-node@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 + +## @backstage/plugin-auth-react@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog@1.22.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + +## @backstage/plugin-catalog-backend@1.25.3-next.1 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @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/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @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/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-azure@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @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/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.3.2-next.1 + +### Patch Changes + +- 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/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @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/integration@1.14.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.2.2-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-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.7.3-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.1 + +### Patch Changes + +- 53b24d9: Internal update to use the new cache manager +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @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/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @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 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @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 + - @backstage/plugin-catalog-node@1.12.7-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.5.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/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-graph@0.4.9-next.1 + +### Patch Changes + +- da91078: Fixed a bug in the `CatalogGraphPage` component where, after clicking on some nodes, clicking the back button would break the navigation. This issue caused the entire navigation to fail and behaved differently across various browsers. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-node@1.12.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-catalog-react@1.12.4-next.1 + +### Patch Changes + +- ae9b6cb: Small internal fix to better work with recent `lodash` versions +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.0 + +### Patch Changes + +- 4f08c85: Show additional info on DevTools unprocessed entities table + + - Location path (so that it's easier to search the failed entity from the YAML URL) + - Time info of last discovery and next refresh time so that users can be aware of it and can sort the errors based on the time. + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-config-schema@0.1.59-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-devtools-backend@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-events-backend@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-github@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-events-node@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + +## @backstage/plugin-home@0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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/theme@0.5.6 + - @backstage/plugin-home-react@0.1.17-next.0 + +## @backstage/plugin-home-react@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes@0.11.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + +## @backstage/plugin-kubernetes-backend@0.18.6-next.1 + +### Patch Changes + +- ca96b66: Skip start without proper config +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-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/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-node@0.1.19-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + +## @backstage/plugin-kubernetes-node@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-react@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-notifications@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-react@0.0.5-next.0 + +## @backstage/plugin-notifications-backend@0.4.0-next.1 + +### Patch Changes + +- f195972: Validate notification link when new notification is created +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-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/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.0-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- 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/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + +## @backstage/plugin-notifications-node@0.2.6-next.1 + +### Patch Changes + +- 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/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## @backstage/plugin-org@0.6.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-org-react@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@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 + +## @backstage/plugin-permission-backend-module-allow-all-policy@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/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + +## @backstage/plugin-permission-node@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 + +## @backstage/plugin-proxy-backend@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder@1.25.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.1 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-backend@1.25.0-next.1 + +### Patch Changes + +- f865103: Updated dependency `esbuild` to `^0.23.0`. +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-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/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.1 + - @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-backend-module-gerrit@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@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 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.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-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @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/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @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-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @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/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.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/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.1 + +## @backstage/plugin-scaffolder-node@0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-test-utils@0.6.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + +## @backstage/plugin-scaffolder-react@1.12.0-next.1 + +### Patch Changes + +- c2cbe1e: Updated dependency `use-immer` to `^0.10.0`. +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-search@1.4.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + +## @backstage/plugin-search-backend@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 + +## @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + +### Patch Changes + +- 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/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-pg@0.5.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + +### Patch Changes + +- 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/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## @backstage/plugin-search-backend-node@1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-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-search-common@1.2.14 + +## @backstage/plugin-search-react@1.8.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-signals@0.0.10-next.1 + +### Patch Changes + +- 3e9b1a4: Put a name on the `SignalsDisplay` component extension +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.5-next.0 + +## @backstage/plugin-signals-backend@0.2.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/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## @backstage/plugin-signals-node@0.1.11-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/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + +## @backstage/plugin-techdocs@1.10.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.6-next.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-search-react@1.8.0-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @backstage/plugin-techdocs-backend@1.10.13-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- 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/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @backstage/plugin-techdocs-node@1.12.11-next.1 + +### Patch Changes + +- Updated dependencies + - @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/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-react@1.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.5-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.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/types@1.1.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@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 + +## example-app-next@0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/plugin-app@0.1.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-app-visualizer@0.1.10-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-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 + +## app-next-example-plugin@0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + +## example-backend@0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-notifications-backend@0.4.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + +## example-backend-legacy@0.2.102-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + +## e2e-test@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/core-components@0.14.11-next.0 + - @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/test-utils@1.6.0-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + +## @internal/plugin-todo-list@1.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @internal/plugin-todo-list-backend@1.0.31-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/errors@1.2.4 diff --git a/docs/releases/v1.31.0-next.2-changelog.md b/docs/releases/v1.31.0-next.2-changelog.md new file mode 100644 index 0000000000..1547a48332 --- /dev/null +++ b/docs/releases/v1.31.0-next.2-changelog.md @@ -0,0 +1,2415 @@ +# Release v1.31.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.31.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.31.0-next.2) + +## @backstage/backend-app-api@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 + +- 6ed9264: chore(deps): bump `path-to-regexp` from 6.2.2 to 8.0.0 +- 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-loader@1.9.1-next.0 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-plugin-api@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 + +- 8052b9b: Add a `toJSON` on refs so that they can appear in expectations in jest tests +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/backend-test-utils@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 + +## @backstage/backend-defaults@0.5.0-next.2 + +### Minor Changes + +- baeef13: **BREAKING** Removed `createLifecycleMiddleware` and `LifecycleMiddlewareOptions` to clean up API surface. These exports have no external usage and do not provide value in its current form. If you were using these exports, please reach out to the maintainers to discuss your use case. + +### Patch Changes + +- 6ed9264: chore(deps): bump `path-to-regexp` from 6.2.2 to 8.0.0 +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-app-api@1.0.0-next.2 + - @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/cli-node@0.2.8-next.0 + - @backstage/integration@1.15.0-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/catalog-client@1.7.0-next.1 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/dev-utils@1.1.0-next.2 + +### Minor Changes + +- 10b1452: Allow using translations in `createDevApp` + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/app-defaults@1.5.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/catalog-model@1.6.0 + +## @backstage/integration@1.15.0-next.0 + +### Minor Changes + +- 1573014: The Gerrit integration can now resolve Gitiles urls that point to the following + refs: + + - HEAD + - A SHA + - Tag + - Branch + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.1.0-next.0 + +### Minor Changes + +- d908d8c: New module for `@backstage/plugin-auth-backend` that adds a Auth0 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-bitbucket-server-provider@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-catalog@1.23.0-next.2 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-catalog-backend@1.26.0-next.2 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- 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/integration@1.15.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.2 + +## @backstage/plugin-catalog-react@1.13.0-next.2 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 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-components@0.14.11-next.1 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-scaffolder@1.25.0-next.2 + +### Minor Changes + +- 860ad3a: Expose styles for TemplateEditor, TemplateFormPreviewer and CustomFieldExplorer +- 4baad34: Added support for `omitExtraData` and `liveOmit` for rjsf in the scaffolder + +### Patch Changes + +- b0a5c9f: The `ui:options` for `OwnedEntityPicker` field are now passed to `EntityPicker`. This allows you to use any `ui:options` which `EntityPicker` accepts in the `OwnedEntityPicker` field including `allowArbitraryValues` and `defaultNamespace`. +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- fa9d8da: Updated dependency `@rjsf/utils` to `5.20.1`. + Updated dependency `@rjsf/core` to `5.20.1`. + Updated dependency `@rjsf/material-ui` to `5.20.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.20.1`. +- 0944334: Removed duplicated titles on Scaffolder `TemplateListPage` component +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/plugin-scaffolder-react@1.12.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/integration@1.15.0-next.0 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-backend@1.25.0-next.2 + +### Minor Changes + +- df9ae9e: Added scaffolder action publish:bitbucketCloud:pull-request + +### Patch Changes + +- f0c6b25: Allow listing file contents with `debug:log` scaffolder action +- 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/plugin-scaffolder-backend-module-gitlab@0.5.0-next.2 + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.23-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.0-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.2 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-react@1.12.0-next.2 + +### Minor Changes + +- 4baad34: Added support for `omitExtraData` and `liveOmit` for rjsf in the scaffolder + +### Patch Changes + +- 1f3c5aa: Fix scaffolder review step issue where schema options are not handled for fields on multi-step templates. +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- fa9d8da: Updated dependency `@rjsf/utils` to `5.20.1`. + Updated dependency `@rjsf/core` to `5.20.1`. + Updated dependency `@rjsf/material-ui` to `5.20.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.20.1`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/app-defaults@1.5.11-next.1 + +### Patch Changes + +- 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-components@0.14.11-next.1 + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + +## @backstage/backend-common@0.25.0-next.2 + +### Patch Changes + +- 6ed9264: chore(deps): bump `path-to-regexp` from 6.2.2 to 8.0.0 +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- 3606843: Internal fixes to match `testcontainers` update +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-dynamic-feature-service@0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.0.0-next.2 + - @backstage/backend-common@0.25.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/cli-node@0.2.8-next.0 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/plugin-events-backend@0.3.12-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/backend-openapi-utils@0.1.18-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/errors@1.2.4 + +## @backstage/cli@0.27.1-next.2 + +### Patch Changes + +- 16ffdd6: Remove direct `vite` dependency + +- 8069f4a: Update Scaffolder module template to add itself to the backend + +- 0e1a817: The app build process now outputs an additional `index.html.tmpl` file. This is an non-templated version of the `index.html` file, which can be used to delay templating until runtime. + + The new `index.html.tmpl` file also sets a `backstage-public-path` meta tag to be templated at runtime. The meta tag is in turn picked up by the new `@backstage/cli/config/webpack-public-path.js` entry point script, which uses it to set the runtime public path of the Webpack bundle. + +- d29fc1b: Updated dependency `@module-federation/enhanced` to `^0.6.0`. + +- 4ebf36f: Upgrade to `vite@v5` + +- 2d3caaf: The build commands now support the new `backstage.inline` flag in `package.json`, which causes the contents of private packages to be inlined into the consuming package, rather than be treated as an external dependency. + +- 3d88455: Add support for `backstage:^` version ranges to versions:bump when using the experimental yarn plugin + +- Updated dependencies + - @backstage/cli-node@0.2.8-next.0 + - @backstage/integration@1.15.0-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/eslint-plugin@0.1.9-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/cli-node@0.2.8-next.0 + +### Patch Changes + +- 0c70f43: Add definition for the new `backstage.inline` field in `package.json`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/config-loader@1.9.1-next.0 + +### Patch Changes + +- ef3c507: Updated dependency `typescript-json-schema` to `^0.65.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/core-app-api@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 + +## @backstage/core-compat-api@0.3.0-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + +## @backstage/core-components@0.14.11-next.1 + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 0944334: Removed default title set to Unknown page on `ContentHeaderTitle` component to support usage of component without title prop. +- f325258: Use getOptionalString for optional app.support.items\[].links\[].title +- af4c146: Support menu items should not be buttons in favour of links +- 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). +- dbbd93e: Internal update to match recent React types +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/core-plugin-api@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 + +## @backstage/create-app@0.5.19-next.2 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/eslint-plugin@0.1.9-next.0 + +### Patch Changes + +- 08895e3: Added support for linting dependencies on workspace packages with the `backstage.inline` flag. + +## @backstage/frontend-app-api@0.9.0-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-defaults@0.1.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/frontend-defaults@0.1.0-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/plugin-app@0.1.0-next.2 + - @backstage/frontend-app-api@0.9.0-next.2 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/frontend-plugin-api@0.8.0-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 043d7cd: Internal refactor +- 2a61422: The `factory` option is no longer required when overriding an extension. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/types@1.1.1 + +## @backstage/frontend-test-utils@0.2.0-next.2 + +### Patch Changes + +- 2a61422: The extension tester will no longer unconditionally enable any additional extensions that have been added. +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 043d7cd: Internal refactor +- Updated dependencies + - @backstage/test-utils@1.6.0-next.1 + - @backstage/plugin-app@0.1.0-next.2 + - @backstage/frontend-app-api@0.9.0-next.2 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/integration-react@1.1.31-next.0 + +### Patch Changes + +- 8a9d797: Remove unnecessary broad permissions from Gitlab `SCMAuth` + + Newer versions of Gitlab (after 2019) do not require the broad api permissions to write to repos. + +- Updated dependencies + - @backstage/integration@1.15.0-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/config@1.2.0 + +## @backstage/repo-tools@0.9.7-next.2 + +### Patch Changes + +- 1a8837e: Avoid generating API reports for packages with `backstage.inline` set. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/cli-node@0.2.8-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-techdocs-node@1.12.11-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.6.0-next.1 + +### Patch Changes + +- 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-app-api@1.14.3-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/theme@0.5.7-next.0 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. + +## @backstage/version-bridge@1.0.9-next.0 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. + +## @backstage/plugin-api-docs@0.11.9-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 8a474f2: Updating docs to use `createFrontendModule` instead +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/plugin-catalog@1.23.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-app@0.1.0-next.2 + +### Patch Changes + +- 57bf6ae: Fix issue with `AlertDisplay` and other components defined with `AppRootElementBlueprint` not being rendered when at the `SignInWrapper` +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + +## @backstage/plugin-app-backend@0.3.74-next.2 + +### Patch Changes + +- 590fb2d: **BREAKING**: The app backend now supports the new `index.html.tmpl` output from `@backstage/cli`. If available, the `index.html` will be templated at runtime with the current configuration of the app backend. + + This is marked as a breaking change because you must now supply the app build-time configuration to the backend. This change also affects the public path behavior, where it is no longer necessary to build the app with the correct public path upfront. You now only need to supply a correct `app.baseUrl` to the app backend plugin at runtime. + + An effect that this change has is that the `index.html` will now contain and present the frontend configuration in an easily readable way, which can aid in debugging. This data was always available in the frontend, but it was injected and hidden in the static bundle. + + This templating behavior is enabled by default, but it can be disabled by setting the `app.disableConfigInjection` configuration option to `true`. + +- 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-loader@1.9.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.2 + +## @backstage/plugin-app-node@0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config-loader@1.9.1-next.0 + +## @backstage/plugin-app-visualizer@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + +## @backstage/plugin-auth-backend@0.23.0-next.2 + +### Patch Changes + +- 527d973: Migrated the `Bitbucket Server` auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-bitbucket-server-provider` module. +- d908d8c: Migrated the `Auth0` auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-auth0-provider` module. +- 92118cd: Updated dependency `@node-saml/passport-saml` to `^5.0.0`. +- 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/catalog-client@1.7.0-next.1 + - @backstage/plugin-auth-backend-module-auth0-provider@0.1.0-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.2 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.1.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@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 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@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 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@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 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@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 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@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 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@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 + +## @backstage/plugin-auth-backend-module-github-provider@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 + +## @backstage/plugin-auth-backend-module-gitlab-provider@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 + +## @backstage/plugin-auth-backend-module-google-provider@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 + +## @backstage/plugin-auth-backend-module-guest-provider@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/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@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 + +## @backstage/plugin-auth-backend-module-oauth2-provider@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 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@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 + +## @backstage/plugin-auth-backend-module-oidc-provider@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 + +## @backstage/plugin-auth-backend-module-okta-provider@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 + +## @backstage/plugin-auth-backend-module-onelogin-provider@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 + +## @backstage/plugin-auth-backend-module-pinniped-provider@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 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@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 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-auth-node@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 + +## @backstage/plugin-auth-react@0.1.6-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-bitbucket-cloud-common@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.15.0-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-azure@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/backend-openapi-utils@0.1.18-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.3.2-next.2 + +### Patch Changes + +- 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/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.23-next.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @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-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.7.3-next.2 + +### Patch Changes + +- 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/plugin-catalog-backend@1.26.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.2 + +### Patch Changes + +- 0476be3: Add the `relations` array to allow Backstage to mirror GitLab's membership behavior, including descendant, inherited, and shared-from-group memberships. + + The previous `allowInherited` config option will be deprecated in future versions. Use the `relations` array with the `INHERITED` option instead. + + ```yaml + catalog: + providers: + gitlab: + development: + relations: + - INHERITED + ``` + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-logs@0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-catalog-backend-module-msgraph@0.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.5.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/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-graph@0.4.9-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 8a474f2: Updating docs to use `createFrontendModule` instead +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.3-next.2 + +### Patch Changes + +- f3c90da: Support button title should be contained in a single menu item +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/integration@1.15.0-next.0 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-node@1.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.8-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-config-schema@0.1.59-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + +## @backstage/plugin-devtools-backend@0.4.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- 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/config-loader@1.9.1-next.0 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-events-backend@0.3.12-next.2 + +### Patch Changes + +- 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/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-backend-module-azure@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-backend-module-gerrit@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-backend-module-github@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-backend-module-gitlab@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-backend-test-utils@0.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-events-node@0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + +## @backstage/plugin-home@0.7.10-next.2 + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- fa9d8da: Updated dependency `@rjsf/utils` to `5.20.1`. + Updated dependency `@rjsf/core` to `5.20.1`. + Updated dependency `@rjsf/material-ui` to `5.20.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.20.1`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-home-react@0.1.17-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-home-react@0.1.17-next.1 + +### Patch Changes + +- fa9d8da: Updated dependency `@rjsf/utils` to `5.20.1`. + Updated dependency `@rjsf/core` to `5.20.1`. + Updated dependency `@rjsf/material-ui` to `5.20.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.20.1`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + +## @backstage/plugin-kubernetes@0.11.14-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-backend@0.18.6-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/catalog-client@1.7.0-next.1 + - @backstage/plugin-kubernetes-node@0.1.19-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-kubernetes-cluster@0.0.15-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/plugin-kubernetes-react@0.4.3-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-node@0.1.19-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-react@0.4.3-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-notifications@0.3.1-next.2 + +### Patch Changes + +- 653f60b: Severity filter label newly contains "Min severity" to better describe range instead of exact value. +- 4a53dd0: Implement icon in backend and show icon in table if available. +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-signals-react@0.0.5-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-notifications-backend@0.4.0-next.2 + +### Patch Changes + +- 4a53dd0: Implement icon in backend and show icon in table if available. +- 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/catalog-client@1.7.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.2 + - @backstage/plugin-signals-node@0.1.11-next.2 + +## @backstage/plugin-notifications-backend-module-email@0.3.0-next.2 + +### Patch Changes + +- 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/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.2 + +## @backstage/plugin-notifications-node@0.2.6-next.2 + +### Patch Changes + +- 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/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.11-next.2 + +## @backstage/plugin-org@0.6.29-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 8a474f2: Updating docs to use `createFrontendModule` instead +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-org-react@0.1.28-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-permission-backend@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 + +## @backstage/plugin-permission-backend-module-allow-all-policy@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/plugin-permission-node@0.8.3-next.2 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-permission-node@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 + +## @backstage/plugin-permission-react@0.4.26-next.0 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-proxy-backend@0.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@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 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.0-next.2 + +### Patch Changes + +- df9ae9e: Added scaffolder action publish:bitbucketCloud:pull-request +- Updated dependencies + - @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-bitbucket-cloud-common@0.2.23-next.0 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- Updated dependencies + - @backstage/backend-common@0.25.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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- 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/integration@1.15.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.2 + +### Patch Changes + +- 22a19e6: Added the ability for the actions `publish:github` and `github:repo:create` to take inputs for 'custom properties' for organization repositories. +- Updated dependencies + - @backstage/backend-common@0.25.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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- Updated dependencies + - @backstage/backend-common@0.25.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-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.2 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- Updated dependencies + - @backstage/backend-common@0.25.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/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + - @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.2 + +## @backstage/plugin-scaffolder-node@0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-test-utils@1.0.0-next.2 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + +## @backstage/plugin-search@1.4.16-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend@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 + +## @backstage/plugin-search-backend-module-catalog@0.2.2-next.2 + +### Patch Changes + +- 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/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-explore@0.2.2-next.2 + +### Patch Changes + +- 276f433: Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.5`. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-pg@0.5.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-module-techdocs@0.2.2-next.2 + +### Patch Changes + +- 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/plugin-techdocs-node@1.12.11-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-backend-node@1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-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 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-search-react@1.8.0-next.2 + +### Patch Changes + +- dbbd93e: Internal update to match recent React types +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.14 + +## @backstage/plugin-signals@0.0.10-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-signals-react@0.0.5-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-signals-backend@0.2.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/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-signals-node@0.1.11-next.2 + +## @backstage/plugin-signals-node@0.1.11-next.2 + +### Patch Changes + +- 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/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.2 + +## @backstage/plugin-signals-react@0.0.5-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-techdocs@1.10.9-next.2 + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 97db53e: Enhanced the table hover effect with a lighter color and updated the border radius to align with Backstage's theme styling +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-auth-react@0.1.6-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.38-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @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/core-app-api@1.14.3-next.0 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/test-utils@1.6.0-next.1 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.2 + +## @backstage/plugin-techdocs-backend@1.10.13-next.2 + +### Patch Changes + +- 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/plugin-techdocs-node@1.12.11-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.2 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.14-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/integration@1.15.0-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.2 + +## @backstage/plugin-techdocs-node@1.12.11-next.2 + +### Patch Changes + +- 57da51f: Add support for mapping custom tags in the techdocs yaml parser that validates the mkdocs.yaml file +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- 3606843: Internal fixes to match `testcontainers` update +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-react@1.2.8-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-user-settings@0.8.12-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- da86ac2: Update README to clarify location of additional tabs example +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-signals-react@0.0.5-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.24-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @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-signals-node@0.1.11-next.2 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@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 + +## example-app-next@0.0.15-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-app@0.1.0-next.2 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-app-api@0.9.0-next.2 + - @backstage/frontend-defaults@0.1.0-next.1 + - @backstage/frontend-plugin-api@0.8.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-app-visualizer@0.1.10-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 + +## app-next-example-plugin@0.0.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.2 + +## example-backend@0.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-devtools-backend@0.4.0-next.2 + - @backstage/plugin-scaffolder-backend@1.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/plugin-catalog-backend@1.26.0-next.2 + - @backstage/plugin-app-backend@0.3.74-next.2 + - @backstage/plugin-notifications-backend@0.4.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.2 + - @backstage/plugin-kubernetes-backend@0.18.6-next.2 + - @backstage/plugin-permission-backend@0.5.49-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/plugin-search-backend@1.5.17-next.2 + - @backstage/plugin-signals-backend@0.2.0-next.2 + - @backstage/plugin-techdocs-backend@1.10.13-next.2 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-proxy-backend@0.5.6-next.2 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.2 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + +## example-backend-legacy@0.2.102-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.2 + - @backstage/plugin-devtools-backend@0.4.0-next.2 + - @backstage/plugin-scaffolder-backend@1.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/catalog-client@1.7.0-next.1 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/plugin-app-backend@0.3.74-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/plugin-events-backend@0.3.12-next.2 + - @backstage/plugin-kubernetes-backend@0.18.6-next.2 + - @backstage/plugin-permission-backend@0.5.49-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/plugin-search-backend@1.5.17-next.2 + - @backstage/plugin-signals-backend@0.2.0-next.2 + - @backstage/plugin-techdocs-backend@1.10.13-next.2 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-proxy-backend@0.5.6-next.2 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.2 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-signals-node@0.1.11-next.2 + +## e2e-test@0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## @internal/frontend@0.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/types@1.1.1 + +## techdocs-cli-embedded-app@0.2.100-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-techdocs@1.10.9-next.2 + - @backstage/plugin-catalog@1.23.0-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/app-defaults@1.5.11-next.1 + - @backstage/test-utils@1.6.0-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + +## @internal/plugin-todo-list@1.0.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + +## @internal/plugin-todo-list-backend@1.0.31-next.2 + +### Patch Changes + +- 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/errors@1.2.4 diff --git a/docs/tooling/local-dev/profiling.md b/docs/tooling/local-dev/profiling.md new file mode 100644 index 0000000000..bbc263c3d5 --- /dev/null +++ b/docs/tooling/local-dev/profiling.md @@ -0,0 +1,65 @@ +--- +id: profiling +title: Profiling Backstage +description: Finding performance bottlenecks in your Backstage application +--- + +Profiling can help you find performance bottlenecks in your code. This guide will show you how to profile +both the backend and frontend of your Backstage application. + +# Backend + +To profile the backend, start the backend with the `--inspect` flag: + +```shell +yarn workspace backend start --inspect +# or `yarn workspace example-backend start --inspect` in this repository +``` + +Next you can use the Chrome DevTools to profile the backend by navigating to `chrome://inspect` and +clicking the `Open dedicated DevTools for Node` link. + +In the `Performance` tab, you can start a new recording by clicking the `Start` button. After you +have recorded some data, you can stop the recording by clicking the `Stop` button. The recording +will show you a flame graph of the backend's execution, which can help you identify performance issues. + +You can also use the `Memory` tab to profile the backend's memory usage and find potential memory leaks. + +It's recommended to start profiling with short periods of time to avoid too much data being collected. + +## Stress testing + +To get more out of profiling, you might want to introduce additional load to your application with some tooling. +One such tool is called [AutoCannon](https://www.npmjs.com/package/autocannon) which can be used to stress test the +API endpoints of your application. You can install it globally with `npm install -g autocannon`. + +To be able to access the API endpoints, you must configure a static token for the backend. In your +`app-config.yaml` file, add the following configuration: + +```yaml +backend: + auth: + externalAccess: + - type: static + options: + token: autocannon12345 + subject: autocannon +``` + +See more information in the [Service to Service Auth](../../auth/service-to-service-auth.md) documentation. +To run the stress test, you can use the following command: + +```shell +autocannon -H "Authorization=Bearer autocannon12345" http://localhost:7007/api/catalog/entities +``` + +See more command options in the AutoCannon documentation. + +# Frontend + +Profiling the frontend can be done by using the `React DevTools` extension for Chrome or Firefox. +The extension is available for download from the Chrome Web Store or the Firefox Add-ons website. + +To start profiling, start the application with `yarn dev` and open inspector in the browser. In the +`Profiler` tab (far to the right), click the `Start profiling` button to start recording. After +you have recorded some data by navigating through the page, click the `Stop profiling` button to stop the recording. diff --git a/docs/tooling/package-metadata.md b/docs/tooling/package-metadata.md index 63ce6d1085..ad9d5ec5a0 100644 --- a/docs/tooling/package-metadata.md +++ b/docs/tooling/package-metadata.md @@ -156,6 +156,26 @@ This field indicates that a package has been renamed and moved to a new location } ``` +### `backstage.inline` + +If set to `true` this field indicates that monorepo package is private and should be inlined into dependent packages rather than being treated as a dependency. This effectively means that all imported code from the inlined package will be copied into the consuming package, once for each package. + +This flag affects various parts of the Backstage tooling, for example the way the Backstage CLI builds work, the way that `@backstage/eslint-plugin` lints dependencies on the package, and how it's treated by `@backstage/repo-tools`. + +The `backstage.inline` field is primarily intended to aid in the implementation of the Backstage core framework in the main Backstage repository, but it can be used in other projects as well. + +Setting this flag also requires the top-level `private` field to be set as well, since inline packages should not be published. + +```js title="Example usage of the backstage.moved field" +{ + "name": "@internal/utils", + "backstage": { + "inline": true + } + ... +} +``` + ## Metadata for Published Packages When publishing a package with the help of the Backstage CLI, there are a number of metadata checks that are performed to ensure that the package is correctly set up for the Backstage ecosystem. These checks are performed by the `backstage-cli package prepack` command, which is used to prepare a package for publishing. These checks can all also be verified separately using the `backstage-cli repo fix --publish` command, and in many cases the required metadata can be generated automatically. It is therefore important to make running the `fix` command part of your workflow in any project that is publishing Backstage packages. diff --git a/docs/tutorials/enable-public-entry.md b/docs/tutorials/enable-public-entry.md index 25d7382f13..1ba4983618 100644 --- a/docs/tutorials/enable-public-entry.md +++ b/docs/tutorials/enable-public-entry.md @@ -106,40 +106,12 @@ That's it! If your app uses the new frontend system, you can still use the public entry point feature. The `index-public-experimental.tsx` file does end up looking a bit different in this case: ```tsx title="in packages/app/src/index-public-experimental.tsx" -import React from 'react'; import ReactDOM from 'react-dom/client'; -import { CookieAuthRedirect } from '@backstage/plugin-auth-react'; -import { createApp } from '@backstage/frontend-app-api'; -import { - coreExtensionData, - createExtension, - createExtensionOverrides, - createSignInPageExtension, -} from '@backstage/frontend-plugin-api'; +import { signInPageModule } from './overrides/SignInPage'; +import { createPublicSignInApp } from '@backstage/frontend-defaults'; -const signInPage = createSignInPageExtension({ - name: 'guest', - loader: async () => props => , -}); - -const authRedirectExtension = createExtension({ - namespace: 'app', - name: 'layout', - attachTo: { id: 'app/root', input: 'children' }, - output: { - element: coreExtensionData.reactElement, - }, - factory: () => ({ - element: , - }), -}); - -const app = createApp({ - features: [ - createExtensionOverrides({ - extensions: [signInPage, authRedirectExtension], - }), - ], +const app = createPublicSignInApp({ + features: [signInPageModule], }); ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/microsite/data/plugins/github-workflows.yaml b/microsite/data/plugins/github-workflows.yaml index 743e7a5b1b..f80e6f3f29 100644 --- a/microsite/data/plugins/github-workflows.yaml +++ b/microsite/data/plugins/github-workflows.yaml @@ -4,7 +4,7 @@ author: VeeCode Platform authorUrl: https://platform.vee.codes/ category: CI/CD description: The Github Workflows plugin provides an alternative for manually triggering GitHub workflows from within your Backstage component. -documentation: https://platform.vee.codes/plugin/Github%20workflows/ +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: diff --git a/microsite/data/plugins/gitlab-pipelines.yaml b/microsite/data/plugins/gitlab-pipelines.yaml index d40419e464..4b77d67e07 100644 --- a/microsite/data/plugins/gitlab-pipelines.yaml +++ b/microsite/data/plugins/gitlab-pipelines.yaml @@ -4,7 +4,7 @@ author: VeeCode Platform authorUrl: https://platform.vee.codes category: CI/CD description: The Gitlab pipelines plugin integrates GitlabCi with its backstage component in a simple way. -documentation: https://platform.vee.codes/plugin/Gitlab%20Pipelines/ +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: diff --git a/microsite/data/plugins/grafana.yaml b/microsite/data/plugins/grafana.yaml index c26d10b751..0637717466 100644 --- a/microsite/data/plugins/grafana.yaml +++ b/microsite/data/plugins/grafana.yaml @@ -4,9 +4,9 @@ author: K-Phoen authorUrl: https://github.com/K-Phoen category: Monitoring description: Associate alerts and dashboards to components. -documentation: https://github.com/K-Phoen/backstage-plugin-grafana/ +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: '@k-phoen/backstage-plugin-grafana' +npmPackageName: '@backstage-community/plugin-grafana' tags: - dashboards - monitoring diff --git a/microsite/data/plugins/hashicorp-terraform.yaml b/microsite/data/plugins/hashicorp-terraform.yaml new file mode 100644 index 0000000000..c254667015 --- /dev/null +++ b/microsite/data/plugins/hashicorp-terraform.yaml @@ -0,0 +1,10 @@ +--- +title: Hashicorp Terraform +author: GlobalLogic UK&I +authorUrl: https://www.globallogic.com +category: Infrastructure +description: Easily view Hashicorp Terraform runs for a specific workspace. +documentation: https://github.com/globallogicuki/globallogic-backstage-plugins/tree/main/plugins/terraform +iconUrl: /img/terraform-logo.svg +npmPackageName: '@globallogicuki/backstage-plugin-terraform' +addedDate: '2024-07-17' diff --git a/microsite/data/plugins/kong-service-manager.yaml b/microsite/data/plugins/kong-service-manager.yaml index 78ff37e36e..2b5f444479 100644 --- a/microsite/data/plugins/kong-service-manager.yaml +++ b/microsite/data/plugins/kong-service-manager.yaml @@ -4,7 +4,7 @@ author: VeeCode Platform authorUrl: https://platform.vee.codes/ category: api-manager description: The Kong Service Manager plugin provides information about the kong service, a list of the routes it has and also offers the possibility of manipulating plugins without leaving the backstage. -documentation: https://platform.vee.codes/plugin/Kong%20Service%20Manager/ +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: diff --git a/microsite/data/plugins/kubernetes-gpt-analyzer.yaml b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml index 78bd0fe4c4..e5de961869 100644 --- a/microsite/data/plugins/kubernetes-gpt-analyzer.yaml +++ b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml @@ -4,7 +4,7 @@ author: VeeCode Platform authorUrl: https://platform.vee.codes/ category: Monitoring description: The Kubernetes GPT Analyzer plug-in uses artificial intelligence with the help of k8s-operator to analyze and optimize your Kubernetes entities, improving the management and performance of your cluster. It makes it easier to detect anomalies and suggest best practices. -documentation: https://platform.vee.codes/plugin/Kubernetes%20GPT%20Analyzer/ +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: diff --git a/microsite/data/plugins/scaffolder-backend-module-webex.yaml b/microsite/data/plugins/scaffolder-backend-module-webex.yaml new file mode 100644 index 0000000000..f2113b2a8d --- /dev/null +++ b/microsite/data/plugins/scaffolder-backend-module-webex.yaml @@ -0,0 +1,11 @@ +--- +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. +documentation: https://github.com/Coderrob/backstage-plugin-scaffolder-backend-module-webex +npmPackageName: '@coderrob/backstage-plugin-scaffolder-backend-module-webex' +addedDate: '2024-09-04' diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index a6909f139f..96edb57dea 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -21,9 +21,38 @@ import { themes } from 'prism-react-renderer'; import type * as Preset from '@docusaurus/preset-classic'; import { Config } from '@docusaurus/types'; +import RedirectPlugin from '@docusaurus/plugin-client-redirects'; + const backstageTheme = themes.vsDark; backstageTheme.plain.backgroundColor = '#232323'; +const useVersionedDocs = require('fs').existsSync('versions.json'); + +// 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. +const PatchedRedirectPlugin: typeof RedirectPlugin = (ctx, opts) => { + const plugin = RedirectPlugin(ctx, opts); + + return { + ...plugin, + async postBuild(...args) { + try { + await plugin.postBuild(...args); + } catch (error) { + if ( + error.message === + 'The redirect plugin is not supposed to override existing files.' + ) { + // Bit of a hack to make sure all remaining redirects are written, since the write uses Promise.all + await new Promise(resolve => setTimeout(resolve, 1000)); + } else { + throw error; + } + } + }, + }; +}; + const config: Config = { title: 'Backstage Software Catalog and Developer Platform', tagline: 'An open source framework for building developer portals', @@ -57,6 +86,26 @@ const config: Config = { editUrl: 'https://github.com/backstage/backstage/edit/master/docs/', path: '../docs', sidebarPath: 'sidebars.json', + ...(useVersionedDocs + ? { + includeCurrentVersion: true, + lastVersion: 'stable', + versions: { + stable: { + label: 'Stable', + path: '/', + banner: 'none', + badge: false, + }, + current: { + label: 'Next', + path: '/next', + banner: 'unreleased', + badge: true, + }, + }, + } + : undefined), }, blog: { path: 'blog', @@ -119,9 +168,11 @@ const config: Config = { }; }, }), - [ - '@docusaurus/plugin-client-redirects', - { + ctx => + PatchedRedirectPlugin(ctx, { + id: '@docusaurus/plugin-client-redirects', + toExtensions: [], + fromExtensions: [], redirects: [ { from: '/docs', @@ -200,8 +251,7 @@ const config: Config = { to: '/docs/backend-system/core-services/url-reader', }, ], - }, - ], + }), [ 'docusaurus-pushfeedback', { @@ -259,6 +309,14 @@ const config: Config = { label: 'Community', position: 'left', }, + ...(useVersionedDocs + ? [ + { + type: 'docsVersionDropdown', + position: 'right' as const, + }, + ] + : []), ], }, image: 'img/sharing-opengraph.png', diff --git a/microsite/sidebars.json b/microsite/sidebars.json index baca4f4db6..4d3761c27d 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,39 +1,4 @@ { - "releases": { - "Release Notes": [ - "releases/v1.30.0", - "releases/v1.29.0", - "releases/v1.28.0", - "releases/v1.27.0", - "releases/v1.26.0", - "releases/v1.25.0", - "releases/v1.24.0", - "releases/v1.23.0", - "releases/v1.22.0", - "releases/v1.21.0", - "releases/v1.20.0", - "releases/v1.19.0", - "releases/v1.18.0", - "releases/v1.17.0", - "releases/v1.16.0", - "releases/v1.15.0", - "releases/v1.14.0", - "releases/v1.13.0", - "releases/v1.12.0", - "releases/v1.11.0", - "releases/v1.10.0", - "releases/v1.9.0", - "releases/v1.8.0", - "releases/v1.7.0", - "releases/v1.6.0", - "releases/v1.5.0", - "releases/v1.4.0", - "releases/v1.3.0", - "releases/v1.2.0", - "releases/v1.1.0", - "releases/v1.0.0" - ] - }, "docs": { "Overview": [ "overview/what-is-backstage", @@ -90,7 +55,8 @@ "features/software-catalog/external-integrations", "features/software-catalog/catalog-customization", "features/software-catalog/software-catalog-api", - "features/software-catalog/creating-the-catalog-graph" + "features/software-catalog/creating-the-catalog-graph", + "features/software-catalog/faq" ] }, { @@ -366,7 +332,8 @@ "label": "Local Development", "items": [ "tooling/local-dev/linking-local-packages", - "tooling/local-dev/debugging" + "tooling/local-dev/debugging", + "tooling/local-dev/profiling" ] }, "tooling/package-metadata" @@ -555,5 +522,40 @@ "contribute/project-structure" ], "References": ["references/glossary"] + }, + "releases": { + "Release Notes": [ + "releases/v1.30.0", + "releases/v1.29.0", + "releases/v1.28.0", + "releases/v1.27.0", + "releases/v1.26.0", + "releases/v1.25.0", + "releases/v1.24.0", + "releases/v1.23.0", + "releases/v1.22.0", + "releases/v1.21.0", + "releases/v1.20.0", + "releases/v1.19.0", + "releases/v1.18.0", + "releases/v1.17.0", + "releases/v1.16.0", + "releases/v1.15.0", + "releases/v1.14.0", + "releases/v1.13.0", + "releases/v1.12.0", + "releases/v1.11.0", + "releases/v1.10.0", + "releases/v1.9.0", + "releases/v1.8.0", + "releases/v1.7.0", + "releases/v1.6.0", + "releases/v1.5.0", + "releases/v1.4.0", + "releases/v1.3.0", + "releases/v1.2.0", + "releases/v1.1.0", + "releases/v1.0.0" + ] } } diff --git a/microsite/static/img/terraform-logo.svg b/microsite/static/img/terraform-logo.svg new file mode 100644 index 0000000000..9ec37bdab4 --- /dev/null +++ b/microsite/static/img/terraform-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/microsite/yarn.lock b/microsite/yarn.lock index d5d78e9371..c619f0e03c 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2715,92 +2715,92 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-darwin-arm64@npm:1.5.29" +"@swc/core-darwin-arm64@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-darwin-arm64@npm:1.7.24" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-darwin-x64@npm:1.5.29" +"@swc/core-darwin-x64@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-darwin-x64@npm:1.7.24" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.5.29" +"@swc/core-linux-arm-gnueabihf@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.7.24" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-linux-arm64-gnu@npm:1.5.29" +"@swc/core-linux-arm64-gnu@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-linux-arm64-gnu@npm:1.7.24" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-linux-arm64-musl@npm:1.5.29" +"@swc/core-linux-arm64-musl@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-linux-arm64-musl@npm:1.7.24" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-linux-x64-gnu@npm:1.5.29" +"@swc/core-linux-x64-gnu@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-linux-x64-gnu@npm:1.7.24" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-linux-x64-musl@npm:1.5.29" +"@swc/core-linux-x64-musl@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-linux-x64-musl@npm:1.7.24" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-win32-arm64-msvc@npm:1.5.29" +"@swc/core-win32-arm64-msvc@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-win32-arm64-msvc@npm:1.7.24" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-win32-ia32-msvc@npm:1.5.29" +"@swc/core-win32-ia32-msvc@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-win32-ia32-msvc@npm:1.7.24" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.5.29": - version: 1.5.29 - resolution: "@swc/core-win32-x64-msvc@npm:1.5.29" +"@swc/core-win32-x64-msvc@npm:1.7.24": + version: 1.7.24 + resolution: "@swc/core-win32-x64-msvc@npm:1.7.24" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.5.29 - resolution: "@swc/core@npm:1.5.29" + version: 1.7.24 + resolution: "@swc/core@npm:1.7.24" dependencies: - "@swc/core-darwin-arm64": 1.5.29 - "@swc/core-darwin-x64": 1.5.29 - "@swc/core-linux-arm-gnueabihf": 1.5.29 - "@swc/core-linux-arm64-gnu": 1.5.29 - "@swc/core-linux-arm64-musl": 1.5.29 - "@swc/core-linux-x64-gnu": 1.5.29 - "@swc/core-linux-x64-musl": 1.5.29 - "@swc/core-win32-arm64-msvc": 1.5.29 - "@swc/core-win32-ia32-msvc": 1.5.29 - "@swc/core-win32-x64-msvc": 1.5.29 + "@swc/core-darwin-arm64": 1.7.24 + "@swc/core-darwin-x64": 1.7.24 + "@swc/core-linux-arm-gnueabihf": 1.7.24 + "@swc/core-linux-arm64-gnu": 1.7.24 + "@swc/core-linux-arm64-musl": 1.7.24 + "@swc/core-linux-x64-gnu": 1.7.24 + "@swc/core-linux-x64-musl": 1.7.24 + "@swc/core-win32-arm64-msvc": 1.7.24 + "@swc/core-win32-ia32-msvc": 1.7.24 + "@swc/core-win32-x64-msvc": 1.7.24 "@swc/counter": ^0.1.3 - "@swc/types": ^0.1.8 + "@swc/types": ^0.1.12 peerDependencies: "@swc/helpers": "*" dependenciesMeta: @@ -2827,7 +2827,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 19e3f8525ce4f4d9f6f3f62825b1f13e1e76146390930b6d5e663c0bb2c5e95ef3bf30268110d1444ea92b99dd95bfe5a3c6e2a3c0dd79291505dc7da1ed3dab + checksum: d46f58a43c8a5067459fec37e9df61f821e0315269165d05c7c173ad2039ca819986d1397b967f17163082c6333cb31293a1870e9b68dd09e03c2fc21d48b723 languageName: node linkType: hard @@ -2838,12 +2838,12 @@ __metadata: languageName: node linkType: hard -"@swc/types@npm:^0.1.8": - version: 0.1.8 - resolution: "@swc/types@npm:0.1.8" +"@swc/types@npm:^0.1.12": + version: 0.1.12 + resolution: "@swc/types@npm:0.1.12" dependencies: "@swc/counter": ^0.1.3 - checksum: e564d0e37b0e28546973c6d50c7a179395912a97168d695cfe9cf1051199c8b828680cdafcb8d43948f76d3703873bafb88dfb5bc2dfe0596b4ad18fcaf90c80 + checksum: cf7f89e46f859864075d7965582baea9c5f98830f45b1046251568c9bdf1ca484b1bf37f6d3c32b7c82ecf8cd5df89d22f05268c391819c44e49911bb1a8e71a languageName: node linkType: hard @@ -3064,9 +3064,9 @@ __metadata: linkType: hard "@types/luxon@npm:^3.0.0": - version: 3.3.8 - resolution: "@types/luxon@npm:3.3.8" - checksum: 026711b4aefc01c6e233e359c693b6d4c0873acc3f52a08661ea3751b2cf3e34d1ce1d5b0b99b0e40a059a2a3755acc20498d37db5f9a5a4f0f330cdf72db3db + version: 3.4.2 + resolution: "@types/luxon@npm:3.4.2" + checksum: 6f92d5bd02e89f310395753506bcd9cef3a56f5940f7a50db2a2b9822bce753553ac767d143cb5b4f9ed5ddd4a84e64f89ff538082ceb4d18739af7781b56925 languageName: node linkType: hard @@ -7516,9 +7516,9 @@ __metadata: linkType: hard "luxon@npm:^3.0.0": - version: 3.4.4 - resolution: "luxon@npm:3.4.4" - checksum: 36c1f99c4796ee4bfddf7dc94fa87815add43ebc44c8934c924946260a58512f0fd2743a629302885df7f35ccbd2d13f178c15df046d0e3b6eb71db178f1c60c + version: 3.5.0 + resolution: "luxon@npm:3.5.0" + checksum: f290fe5788c8e51e748744f05092160d4be12150dca70f9fadc0d233e53d60ce86acd82e7d909a114730a136a77e56f0d3ebac6141bbb82fd310969a4704825b languageName: node linkType: hard @@ -9634,14 +9634,14 @@ __metadata: linkType: hard "prism-react-renderer@npm:^2.1.0, prism-react-renderer@npm:^2.3.0": - version: 2.3.1 - resolution: "prism-react-renderer@npm:2.3.1" + version: 2.4.0 + resolution: "prism-react-renderer@npm:2.4.0" dependencies: "@types/prismjs": ^1.26.0 clsx: ^2.0.0 peerDependencies: react: ">=16.0.0" - checksum: b12a7d502c1e764d94f7d3c84aee9cd6fccc676bb7e21dee94d37eb2e7e62e097a343999e1979887cb83a57cbdea48d2046aa74d07bce05caa25f4c296df30b6 + checksum: d15d944a8cbf05f7b04deecd2cf4ffb08229a6027918641b6f046cd8ab24b65ca4ebe4ac8e95a53a7d7cefb1bba8df3ce394fe4f1d75418e34fa92553dc63ef7 languageName: node linkType: hard @@ -10444,15 +10444,15 @@ __metadata: linkType: hard "sass@npm:^1.57.1": - version: 1.77.8 - resolution: "sass@npm:1.77.8" + version: 1.78.0 + resolution: "sass@npm:1.78.0" dependencies: chokidar: ">=3.0.0 <4.0.0" immutable: ^4.0.0 source-map-js: ">=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 6b5dce17faa1bd1e349b4825bf7f76559a32f3f95d789cd2847623c88ee9635e1485d3458532a05fa5b9134cfbce79a4bad3f13dc63c2433632347674db0abae + checksum: ea856bd224c85d831a5800195750c2dd008d101771d071dbaca886c47fe4f131c30c328755d7a974ad944ba5b3aafa7a9f6010952da306436dcebddb41580e1c languageName: node linkType: hard diff --git a/mkdocs.yml b/mkdocs.yml index 7bc56695b8..d907e580a8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ nav: - External integrations: 'features/software-catalog/external-integrations.md' - Catalog Customization: 'features/software-catalog/catalog-customization.md' - API: 'features/software-catalog/api.md' + - FAQ: 'features/software-catalog/faq.md' - Kubernetes: - Overview: 'features/kubernetes/index.md' - Installation: 'features/kubernetes/installation.md' @@ -182,6 +183,7 @@ nav: - Local Development: - Linking in Local Packages: 'tooling/local-dev/linking-local-packages.md' - Debugging Backstage: 'tooling/local-dev/debugging.md' + - Profiling Backstage: 'tooling/local-dev/profiling.md' - Package Metadata: 'tooling/package-metadata.md' - Deployment: - Deploying Backstage: 'deployment/index.md' diff --git a/package.json b/package.json index fe87d18027..8566829ecf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.31.0-next.0", + "version": "1.31.0-next.2", "private": true, "repository": { "type": "git", @@ -116,7 +116,7 @@ "cross-env": "^7.0.0", "e2e-test": "workspace:*", "eslint": "^8.6.0", - "eslint-plugin-notice": "^0.9.10", + "eslint-plugin-notice": "^1.0.0", "eslint-plugin-react": "^7.28.0", "eslint-plugin-testing-library": "^6.0.0", "fs-extra": "^11.2.0", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 72c172e395..b17fcd19b5 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/app-defaults +## 1.5.11-next.1 + +### Patch Changes + +- 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-components@0.14.11-next.1 + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + +## 1.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + ## 1.5.10 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index ee21affccf..60fafb2e77 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.10", + "version": "1.5.11-next.1", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" @@ -48,7 +48,7 @@ "@backstage/cli": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@types/react": "^16.13.1 || ^17.0.0" }, "peerDependencies": { diff --git a/packages/app-defaults/src/defaults/icons.tsx b/packages/app-defaults/src/defaults/icons.tsx index 1ed5bf03c6..ebda36c131 100644 --- a/packages/app-defaults/src/defaults/icons.tsx +++ b/packages/app-defaults/src/defaults/icons.tsx @@ -36,6 +36,8 @@ import MuiPersonIcon from '@material-ui/icons/Person'; import MuiWarningIcon from '@material-ui/icons/Warning'; import MuiStorageIcon from '@material-ui/icons/Storage'; import MuiFeaturedPlayListIcon from '@material-ui/icons/FeaturedPlayList'; +import Star from '@material-ui/icons/Star'; +import StarBorder from '@material-ui/icons/StarBorder'; export const icons = { brokenImage: MuiBrokenImageIcon as IconComponent, @@ -62,4 +64,6 @@ export const icons = { 'kind:template': MuiFeaturedPlayListIcon as IconComponent, user: MuiPersonIcon as IconComponent, warning: MuiWarningIcon as IconComponent, + star: Star as IconComponent, + unstarred: StarBorder as IconComponent, }; diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index 2893155e97..a46e23303d 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,21 @@ # app-next-example-plugin +## 0.0.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.2 + +## 0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + ## 0.0.15-next.0 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index a923676370..8815bec86e 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-next-example-plugin/package.json @@ -1,6 +1,6 @@ { "name": "app-next-example-plugin", - "version": "0.0.15-next.0", + "version": "0.0.15-next.2", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 2ea7f13f2a..06f2e97c94 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,95 @@ # example-app-next +## 0.0.15-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-app@0.1.0-next.2 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-app-api@0.9.0-next.2 + - @backstage/frontend-defaults@0.1.0-next.1 + - @backstage/frontend-plugin-api@0.8.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-app-visualizer@0.1.10-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.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/plugin-app@0.1.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-app-visualizer@0.1.10-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-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.0.15-next.0 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 8d44e0a0b3..5d6d56c8ad 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.15-next.0", + "version": "0.0.15-next.2", "private": true, "repository": { "type": "git", @@ -21,6 +21,7 @@ "@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:^", @@ -54,23 +55,19 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@octokit/rest": "^19.0.3", - "@vitejs/plugin-react": "^4.0.4", "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-use": "^17.2.4", - "vite": "^4.4.9", - "vite-plugin-html": "^3.2.0", - "vite-plugin-node-polyfills": "^0.22.0", "zen-observable": "^0.10.0" }, "devDependencies": { "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/jquery": "^3.3.34", "@types/react": "*", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 618bc79ca5..0d686cd5a5 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -48,7 +48,7 @@ import { scmIntegrationsApiRef, } from '@backstage/integration-react'; import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; -import { signInPageOverrides } from './overrides/SignInPage'; +import { signInPageModule } from './overrides/SignInPage'; import { convertLegacyPlugin } from '@backstage/core-compat-api'; import { convertLegacyPageExtension } from '@backstage/core-compat-api'; import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; @@ -154,7 +154,7 @@ const app = createApp({ homePlugin, appVisualizerPlugin, kubernetesPlugin, - signInPageOverrides, + signInPageModule, scmModule, notFoundErrorPageModule, customHomePageModule, diff --git a/packages/app-next/src/index-public-experimental.tsx b/packages/app-next/src/index-public-experimental.tsx index 27b2af7d91..f258a660e3 100644 --- a/packages/app-next/src/index-public-experimental.tsx +++ b/packages/app-next/src/index-public-experimental.tsx @@ -14,32 +14,12 @@ * limitations under the License. */ -import React from 'react'; import ReactDOM from 'react-dom/client'; -import { CookieAuthRedirect } from '@backstage/plugin-auth-react'; -import { createApp } from '@backstage/frontend-app-api'; -import { signInPageOverrides } from './overrides/SignInPage'; -import { - coreExtensionData, - createExtension, - createExtensionOverrides, -} from '@backstage/frontend-plugin-api'; +import { signInPageModule } from './overrides/SignInPage'; +import { createPublicSignInApp } from '@backstage/frontend-defaults'; -const authRedirectExtension = createExtension({ - namespace: 'app', - name: 'layout', - attachTo: { id: 'app/root', input: 'children' }, - output: [coreExtensionData.reactElement], - factory: () => [coreExtensionData.reactElement()], -}); - -const app = createApp({ - features: [ - signInPageOverrides, - createExtensionOverrides({ - extensions: [authRedirectExtension], - }), - ], +const app = createPublicSignInApp({ + features: [signInPageModule], }); ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/packages/app-next/src/overrides/SignInPage.tsx b/packages/app-next/src/overrides/SignInPage.tsx index 52dd01fcde..f1e37a241b 100644 --- a/packages/app-next/src/overrides/SignInPage.tsx +++ b/packages/app-next/src/overrides/SignInPage.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { SignInPage } from '@backstage/core-components'; import { SignInPageBlueprint, - createExtensionOverrides, + createFrontendModule, } from '@backstage/frontend-plugin-api'; const signInPage = SignInPageBlueprint.make({ @@ -29,6 +29,7 @@ const signInPage = SignInPageBlueprint.make({ }, }); -export const signInPageOverrides = createExtensionOverrides({ +export const signInPageModule = createFrontendModule({ + pluginId: 'app', extensions: [signInPage], }); diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 2cde44ff46..78dcfe8ac0 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,87 @@ # example-app +## 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 diff --git a/packages/app/package.json b/packages/app/package.json index cae6409f10..64e6067c17 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.101-next.0", + "version": "0.2.101-next.2", "backstage": { "role": "frontend" }, @@ -74,15 +74,15 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@octokit/rest": "^19.0.3", - "@vitejs/plugin-react": "^4.0.4", + "@vitejs/plugin-react": "^4.3.1", "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-use": "^17.2.4", - "vite": "^4.4.9", - "vite-plugin-html": "^3.2.0", + "vite": "^5.0.0", + "vite-plugin-html": "^3.2.2", "vite-plugin-node-polyfills": "^0.22.0", "zen-observable": "^0.10.0" }, @@ -91,7 +91,7 @@ "@playwright/test": "^1.32.3", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/jquery": "^3.3.34", "@types/react": "*", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index ba556e8616..2b0bb00929 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -117,7 +117,10 @@ const routes = ( }> {homePage} - } /> + } + /> } diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 662b85702f..b5b01f17b5 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/backend-app-api +## 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 https://github.com/backstage/backstage/issues/26353 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 + +- 6ed9264: chore(deps): bump `path-to-regexp` from 6.2.2 to 8.0.0 +- 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-loader@1.9.1-next.0 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.10.0-next.1 + +### Patch Changes + +- c246372: Updated the error message for missing service dependencies to include the plugin and module IDs. +- 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/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + ## 0.10.0-next.0 ### Minor Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 8168bae97f..484209c2c1 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-app-api", - "version": "0.10.0-next.0", + "version": "1.0.0-next.2", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" @@ -76,7 +76,7 @@ "morgan": "^1.10.0", "node-fetch": "^2.7.0", "node-forge": "^1.3.1", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "^8.0.0", "selfsigned": "^2.0.0", "stoppable": "^1.1.0", "triple-beam": "^1.4.1", @@ -96,7 +96,7 @@ "@types/stoppable": "^1.1.0", "http-errors": "^2.0.0", "msw": "^1.0.0", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 3795ae2730..0d38f96f21 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/backend-common +## 0.25.0-next.2 + +### Patch Changes + +- 6ed9264: chore(deps): bump `path-to-regexp` from 6.2.2 to 8.0.0 +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- 3606843: Internal fixes to match `testcontainers` update +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.25.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 988c145: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + - The deprecated `dropDatabase` function has now been removed, without replacement. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + ## 0.25.0-next.0 ### Minor Changes diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 7de2731518..1a67d4877f 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -65,10 +65,15 @@ export type CacheClientOptions = CacheServiceOptions; // @public @deprecated (undocumented) export type CacheClientSetOptions = CacheServiceSetOptions; -// Warning: (ae-forgotten-export) The symbol "CacheManager_2" needs to be exported by the entry point index.d.ts -// // @public @deprecated (undocumented) -export class CacheManager extends CacheManager_2 {} +export class CacheManager { + // (undocumented) + forPlugin(pluginId: string): PluginCacheManager; + static fromConfig( + config: RootConfigService, + options?: CacheManagerOptions, + ): CacheManager; +} // Warning: (ae-forgotten-export) The symbol "CacheManagerOptions_2" needs to be exported by the entry point index.d.ts // @@ -157,7 +162,10 @@ export class DatabaseManager implements LegacyRootDatabaseService { // (undocumented) static fromConfig( config: Config, - options?: DatabaseManagerOptions, + options?: { + migrations?: DatabaseService['migrations']; + logger?: LoggerService; + }, ): DatabaseManager; } @@ -173,11 +181,6 @@ export class DockerContainerRunner implements ContainerRunner { runContainer(options: RunContainerOptions): Promise; } -// Warning: (ae-forgotten-export) The symbol "dropDatabase_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const dropDatabase: typeof dropDatabase_2; - // @public @deprecated export function errorHandler( options?: ErrorHandlerOptions, @@ -274,12 +277,7 @@ export class Git { // @public @deprecated class HostDiscovery implements DiscoveryService { - static fromConfig( - config: Config, - options?: { - basePath?: string; - }, - ): HostDiscovery; + static fromConfig(config: Config): HostDiscovery; // (undocumented) getBaseUrl(pluginId: string): Promise; // (undocumented) @@ -367,10 +365,10 @@ export const legacyPlugin: ( }>, ) => BackendFeature; -// Warning: (ae-forgotten-export) The symbol "LegacyRootDatabaseService_2" needs to be exported by the entry point index.d.ts -// // @public @deprecated (undocumented) -export type LegacyRootDatabaseService = LegacyRootDatabaseService_2; +export type LegacyRootDatabaseService = { + forPlugin(pluginId: string): DatabaseService; +}; // @public @deprecated export function loadBackendConfig(options: { @@ -413,10 +411,10 @@ export function makeLegacyPlugin< // @public @deprecated export function notFoundHandler(): RequestHandler; -// Warning: (ae-forgotten-export) The symbol "PluginCacheManager_2" needs to be exported by the entry point index.d.ts -// // @public @deprecated (undocumented) -export type PluginCacheManager = PluginCacheManager_2; +export type PluginCacheManager = { + getClient(options?: CacheServiceOptions): CacheService; +}; // @public @deprecated (undocumented) export type PluginDatabaseManager = DatabaseService; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 74fa058cc0..d25c965243 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.25.0-next.0", + "version": "0.25.0-next.2", "description": "Common functionality library for Backstage backends", "backstage": { "role": "node-library" @@ -105,7 +105,7 @@ "node-fetch": "^2.7.0", "node-forge": "^1.3.1", "p-limit": "^3.1.0", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "^8.0.0", "pg": "^8.11.3", "pg-format": "^1.0.4", "raw-body": "^2.4.1", @@ -142,7 +142,7 @@ "http-errors": "^2.0.0", "msw": "^1.0.0", "mysql2": "^3.0.0", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "peerDependencies": { "pg-connection-string": "^2.3.0" diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts index 5dfcd322ed..f769d4db09 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -23,17 +23,12 @@ import { HostDiscovery as _HostDiscovery } from '../../../backend-defaults/src/e import { CacheManager as _CacheManager } from '../../../backend-defaults/src/entrypoints/cache/CacheManager'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - type PluginCacheManager as _PluginCacheManager, - type CacheManagerOptions as _CacheManagerOptions, -} from '../../../backend-defaults/src/entrypoints/cache/types'; +import { type CacheManagerOptions as _CacheManagerOptions } from '../../../backend-defaults/src/entrypoints/cache/types'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { - dropDatabase as _dropDatabase, DatabaseManager as _DatabaseManager, type DatabaseManagerOptions as _DatabaseManagerOptions, - type LegacyRootDatabaseService as _LegacyRootDatabaseService, } from '../../../backend-defaults/src/entrypoints/database/DatabaseManager'; import { @@ -48,6 +43,9 @@ import { isChildPath as _isChildPath, LifecycleService, PluginMetadataService, + DatabaseService, + LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; export * from './hot'; @@ -98,11 +96,11 @@ export class HostDiscovery implements DiscoveryService { * plugins: [search] * ``` * - * The basePath defaults to `/api`, meaning the default full internal + * The fixed base path is `/api`, meaning the default full internal * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`. */ - static fromConfig(config: Config, options?: { basePath?: string }) { - return new HostDiscovery(_HostDiscovery.fromConfig(config, options)); + static fromConfig(config: Config) { + return new HostDiscovery(_HostDiscovery.fromConfig(config)); } private constructor(private readonly impl: _HostDiscovery) {} @@ -133,7 +131,31 @@ export { HostDiscovery as SingleHostDiscovery }; * @public * @deprecated Use `CacheManager` from the `@backstage/backend-defaults` package instead */ -export class CacheManager extends _CacheManager {} +export class CacheManager { + /** + * Creates a new {@link CacheManager} instance by reading from the `backend` + * config section, specifically the `.cache` key. + * + * @param config - The loaded application configuration. + */ + static fromConfig( + config: RootConfigService, + options: CacheManagerOptions = {}, + ): CacheManager { + return new CacheManager(_CacheManager.fromConfig(config, options)); + } + + private constructor(private readonly _impl: _CacheManager) {} + + forPlugin(pluginId: string): PluginCacheManager { + return { + getClient: options => { + const result = this._impl.forPlugin(pluginId); + return options ? result.withOptions(options) : result; + }, + }; + } +} /** * @public @@ -145,7 +167,9 @@ export type CacheManagerOptions = _CacheManagerOptions; * @public * @deprecated Use `PluginCacheManager` from the `@backstage/backend-defaults` package instead */ -export type PluginCacheManager = _PluginCacheManager; +export type PluginCacheManager = { + getClient(options?: CacheServiceOptions): CacheService; +}; /** * @public @@ -170,14 +194,20 @@ export type CacheClientOptions = CacheServiceOptions; * @deprecated Use `DatabaseManager` from the `@backstage/backend-defaults` package instead */ export class DatabaseManager implements LegacyRootDatabaseService { - private constructor(private readonly _databaseManager: _DatabaseManager) {} + private constructor( + private readonly _databaseManager: _DatabaseManager, + private readonly logger?: LoggerService, + ) {} static fromConfig( config: Config, - options?: DatabaseManagerOptions, + options?: { + migrations?: DatabaseService['migrations']; + logger?: LoggerService; + }, ): DatabaseManager { const _databaseManager = _DatabaseManager.fromConfig(config, options); - return new DatabaseManager(_databaseManager); + return new DatabaseManager(_databaseManager, options?.logger); } forPlugin( @@ -186,7 +216,20 @@ export class DatabaseManager implements LegacyRootDatabaseService { | { lifecycle: LifecycleService; pluginMetadata: PluginMetadataService } | undefined, ): PluginDatabaseManager { - return this._databaseManager.forPlugin(pluginId, deps); + const logger: LoggerService = this.logger ?? { + debug() {}, + info() {}, + warn() {}, + error() {}, + child() { + return this; + }, + }; + const lifecycle: LifecycleService = deps?.lifecycle ?? { + addShutdownHook() {}, + addStartupHook() {}, + }; + return this._databaseManager.forPlugin(pluginId, { logger, lifecycle }); } } @@ -204,15 +247,11 @@ export type PluginDatabaseManager = _PluginDatabaseManager; /** * @public - * @deprecated Use `LegacyRootDatabaseService` from the `@backstage/backend-defaults` package instead + * @deprecated Use `DatabaseManager` from `@backstage/backend-defaults/database` instead, or migrate to the new backend system and use `coreServices.database` */ -export type LegacyRootDatabaseService = _LegacyRootDatabaseService; - -/** - * @public - * @deprecated Use `dropDatabase` from the `@backstage/backend-defaults` package instead - */ -export const dropDatabase = _dropDatabase; +export type LegacyRootDatabaseService = { + forPlugin(pluginId: string): DatabaseService; +}; /** * @public diff --git a/packages/backend-common/src/deprecated/util/DockerContainerRunner.ts b/packages/backend-common/src/deprecated/util/DockerContainerRunner.ts index ea576e4a3a..a9f5fb0432 100644 --- a/packages/backend-common/src/deprecated/util/DockerContainerRunner.ts +++ b/packages/backend-common/src/deprecated/util/DockerContainerRunner.ts @@ -64,11 +64,19 @@ export class DockerContainerRunner implements ContainerRunner { if (pullImage) { await new Promise((resolve, reject) => { this.dockerClient.pull(imageName, pullOptions, (err, stream) => { - if (err) return reject(err); - stream.pipe(logStream, { end: false }); - stream.on('end', () => resolve()); - stream.on('error', (error: Error) => reject(error)); - return undefined; + if (err) { + reject(err); + } else if (!stream) { + reject( + new Error( + 'Unexpeected error: no stream returned from Docker while pulling image', + ), + ); + } else { + stream.pipe(logStream, { end: false }); + stream.on('end', () => resolve()); + stream.on('error', (error: Error) => reject(error)); + } }); }); } @@ -99,7 +107,7 @@ export class DockerContainerRunner implements ContainerRunner { } // Create docker environment variables array - const Env = []; + const Env = new Array(); for (const [key, value] of Object.entries(envVars)) { Env.push(`${key}=${value}`); } diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index dfa2373019..a8e82623f5 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/backend-defaults +## 0.5.0-next.2 + +### Minor Changes + +- baeef13: **BREAKING** Removed `createLifecycleMiddleware` and `LifecycleMiddlewareOptions` to clean up API surface. These exports have no external usage and do not provide value in its current form. If you were using these exports, please reach out to the maintainers to discuss your use case. + +### Patch Changes + +- 6ed9264: chore(deps): bump `path-to-regexp` from 6.2.2 to 8.0.0 +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-app-api@1.0.0-next.2 + - @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/cli-node@0.2.8-next.0 + - @backstage/integration@1.15.0-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.5.0-next.1 + +### Minor Changes + +- a4bac3c: **BREAKING**: You can no longer supply a `basePath` option to the host discovery implementation. In the new backend system, the ability to choose this path has been removed anyway at the plugin router level. +- 055b75b: **BREAKING**: Simplifications and cleanup as part of the Backend System 1.0 work. + + For the `/database` subpath exports: + + - The deprecated `dropDatabase` function has now been removed, without replacement. + - The deprecated `LegacyRootDatabaseService` type has now been removed. + - The return type from `DatabaseManager.forPlugin` is now directly a `DatabaseService`, as arguably expected. + - `DatabaseManager.forPlugin` now requires the `deps` argument, with the logger and lifecycle services. + + For the `/cache` subpath exports: + + - The `PluginCacheManager` type has been removed. You can still import it from `@backstage/backend-common`, but it's deprecated there, and you should move off of that package by migrating fully to the new backend system. + - Accordingly, `CacheManager.forPlugin` immediately returns a `CacheService` instead of a `PluginCacheManager`. The outcome of this is that you no longer need to make the extra `.getClient()` call. The old `CacheManager` with the old behavior still exists on `@backstage/backend-common`, but the above recommendations apply. + +### Patch Changes + +- 622360e: Move down the discovery config to be in the root +- fe6fd8c: Accept `ConfigService` instead of `Config` in constructors/factories +- 5705424: Wrap scheduled tasks from the scheduler core service now in OpenTelemetry spans +- b2a329d: Properly indent the config schema +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + ## 0.5.0-next.0 ### Minor Changes diff --git a/packages/backend-defaults/api-report-cache.md b/packages/backend-defaults/api-report-cache.md index 58106430ab..ecda42f983 100644 --- a/packages/backend-defaults/api-report-cache.md +++ b/packages/backend-defaults/api-report-cache.md @@ -4,16 +4,15 @@ ```ts import { CacheService } from '@backstage/backend-plugin-api'; -import { CacheServiceOptions } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public export class CacheManager { - forPlugin(pluginId: string): PluginCacheManager; + forPlugin(pluginId: string): CacheService; static fromConfig( - config: Config, + config: RootConfigService, options?: CacheManagerOptions, ): CacheManager; } @@ -31,11 +30,5 @@ export const cacheServiceFactory: ServiceFactory< 'singleton' >; -// @public (undocumented) -export interface PluginCacheManager { - // (undocumented) - getClient(options?: CacheServiceOptions): CacheService; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-database.md b/packages/backend-defaults/api-report-database.md index 10de8df1ff..d9e9e9e287 100644 --- a/packages/backend-defaults/api-report-database.md +++ b/packages/backend-defaults/api-report-database.md @@ -3,24 +3,23 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Config } from '@backstage/config'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginMetadataService } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export class DatabaseManager implements LegacyRootDatabaseService { +export class DatabaseManager { forPlugin( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): DatabaseService; static fromConfig( - config: Config, + config: RootConfigService, options?: DatabaseManagerOptions, ): DatabaseManager; } @@ -28,7 +27,6 @@ export class DatabaseManager implements LegacyRootDatabaseService { // @public export type DatabaseManagerOptions = { migrations?: DatabaseService['migrations']; - logger?: LoggerService; }; // @public @@ -38,16 +36,5 @@ export const databaseServiceFactory: ServiceFactory< 'singleton' >; -// @public @deprecated -export function dropDatabase( - dbConfig: Config, - ...databaseNames: string[] -): Promise; - -// @public -export type LegacyRootDatabaseService = { - forPlugin(pluginId: string): DatabaseService; -}; - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-discovery.md b/packages/backend-defaults/api-report-discovery.md index de60256f02..d168d33a71 100644 --- a/packages/backend-defaults/api-report-discovery.md +++ b/packages/backend-defaults/api-report-discovery.md @@ -3,8 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Config } from '@backstage/config'; import { DiscoveryService } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public @@ -16,12 +16,7 @@ export const discoveryServiceFactory: ServiceFactory< // @public export class HostDiscovery implements DiscoveryService { - static fromConfig( - config: Config, - options?: { - basePath?: string; - }, - ): HostDiscovery; + static fromConfig(config: RootConfigService): HostDiscovery; // (undocumented) getBaseUrl(pluginId: string): Promise; // (undocumented) diff --git a/packages/backend-defaults/api-report-httpRouter.md b/packages/backend-defaults/api-report-httpRouter.md index dc612377ae..97805bc926 100644 --- a/packages/backend-defaults/api-report-httpRouter.md +++ b/packages/backend-defaults/api-report-httpRouter.md @@ -4,16 +4,8 @@ ```ts import { HttpRouterService } from '@backstage/backend-plugin-api'; -import { HumanDuration } from '@backstage/types'; -import { LifecycleService } from '@backstage/backend-plugin-api'; -import { RequestHandler } from 'express'; import { ServiceFactory } from '@backstage/backend-plugin-api'; -// @public -export function createLifecycleMiddleware( - options: LifecycleMiddlewareOptions, -): RequestHandler; - // @public export const httpRouterServiceFactory: ServiceFactory< HttpRouterService, @@ -21,12 +13,5 @@ export const httpRouterServiceFactory: ServiceFactory< 'singleton' >; -// @public -export interface LifecycleMiddlewareOptions { - // (undocumented) - lifecycle: LifecycleService; - startupRequestPauseTimeout?: HumanDuration; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-urlReader.md b/packages/backend-defaults/api-report-urlReader.md index 73fbcd60ac..f8b8db2954 100644 --- a/packages/backend-defaults/api-report-urlReader.md +++ b/packages/backend-defaults/api-report-urlReader.md @@ -12,7 +12,6 @@ import { AzureIntegration } from '@backstage/integration'; import { BitbucketCloudIntegration } from '@backstage/integration'; import { BitbucketIntegration } from '@backstage/integration'; import { BitbucketServerIntegration } from '@backstage/integration'; -import { Config } from '@backstage/config'; import { GerritIntegration } from '@backstage/integration'; import { GiteaIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -21,6 +20,7 @@ import { GitLabIntegration } from '@backstage/integration'; import { HarnessIntegration } from '@backstage/integration'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Readable } from 'stream'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -360,7 +360,7 @@ export class HarnessUrlReader implements UrlReaderService { // @public export type ReaderFactory = (options: { - config: Config; + config: RootConfigService; logger: LoggerService; treeResponseFactory: ReadTreeResponseFactory; }) => UrlReaderPredicateTuple[]; @@ -442,7 +442,7 @@ export const urlReaderServiceFactory: ServiceFactory< // @public export type UrlReadersOptions = { - config: Config; + config: RootConfigService; logger: LoggerService; factories?: ReaderFactory[]; }; diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 60a7ce155c..595cce2f63 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.5.0-next.0", + "version": "0.5.0-next.2", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -169,7 +169,7 @@ "node-fetch": "^2.7.0", "node-forge": "^1.3.1", "p-limit": "^3.1.0", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "^8.0.0", "pg": "^8.11.3", "pg-connection-string": "^2.3.0", "pg-format": "^1.0.4", @@ -198,7 +198,7 @@ "aws-sdk-client-mock": "^4.0.0", "http-errors": "^2.0.0", "msw": "^1.0.0", - "supertest": "^6.1.3", + "supertest": "^7.0.0", "wait-for-expect": "^3.0.2" }, "configSchema": "config.d.ts" diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts index 6a1dad2c91..997739faa2 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts @@ -52,10 +52,10 @@ describe('CacheManager integration', () => { }), ); - manager.forPlugin('p1').getClient(); - manager.forPlugin('p1').getClient({ defaultTtl: 200 }); - manager.forPlugin('p2').getClient(); - manager.forPlugin('p3').getClient({}); + manager.forPlugin('p1'); + manager.forPlugin('p1').withOptions({ defaultTtl: 200 }); + manager.forPlugin('p2'); + manager.forPlugin('p3').withOptions({}); if (store === 'redis') { // eslint-disable-next-line jest/no-conditional-expect @@ -80,9 +80,11 @@ describe('CacheManager integration', () => { }), ); - const plugin1 = manager.forPlugin('p1').getClient(); - const plugin2a = manager.forPlugin('p2').getClient(); - const plugin2b = manager.forPlugin('p2').getClient({ defaultTtl: 2000 }); + const plugin1 = manager.forPlugin('p1'); + const plugin2a = manager.forPlugin('p2'); + const plugin2b = manager + .forPlugin('p2') + .withOptions({ defaultTtl: 2000 }); await plugin1.set('a', 'plugin1'); await plugin2a.set('a', 'plugin2a'); @@ -114,9 +116,9 @@ describe('CacheManager integration', () => { }), ).forPlugin('p'); - const defaultClient = manager.getClient(); - const numberOverrideClient = manager.getClient({ defaultTtl: 400 }); - const durationOverrideClient = manager.getClient({ + const defaultClient = manager; + const numberOverrideClient = manager.withOptions({ defaultTtl: 400 }); + const durationOverrideClient = manager.withOptions({ defaultTtl: { milliseconds: 400 }, }); diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts index 9dd42862c9..41fe595123 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts @@ -15,17 +15,14 @@ */ import { + CacheService, CacheServiceOptions, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import Keyv from 'keyv'; import { DefaultCacheClient } from './CacheClient'; -import { - CacheManagerOptions, - PluginCacheManager, - ttlToMilliseconds, -} from './types'; +import { CacheManagerOptions, ttlToMilliseconds } from './types'; import { durationToMilliseconds } from '@backstage/types'; type StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv; @@ -62,7 +59,7 @@ export class CacheManager { * @param config - The loaded application configuration. */ static fromConfig( - config: Config, + config: RootConfigService, options: CacheManagerOptions = {}, ): CacheManager { // If no `backend.cache` config is provided, instantiate the CacheManager @@ -129,24 +126,16 @@ export class CacheManager { * @param pluginId - The plugin that the cache manager should be created for. * Plugin names should be unique. */ - forPlugin(pluginId: string): PluginCacheManager { - return { - getClient: (defaultOptions = {}) => { - const clientFactory = (options: CacheServiceOptions) => { - const ttl = options.defaultTtl ?? this.defaultTtl; - return this.getClientWithTtl( - pluginId, - ttl !== undefined ? ttlToMilliseconds(ttl) : undefined, - ); - }; - - return new DefaultCacheClient( - clientFactory(defaultOptions), - clientFactory, - defaultOptions, - ); - }, + forPlugin(pluginId: string): CacheService { + const clientFactory = (options: CacheServiceOptions) => { + const ttl = options.defaultTtl ?? this.defaultTtl; + return this.getClientWithTtl( + pluginId, + ttl !== undefined ? ttlToMilliseconds(ttl) : undefined, + ); }; + + return new DefaultCacheClient(clientFactory({}), clientFactory, {}); } private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv { diff --git a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts index f16b306d81..352cde44d7 100644 --- a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts @@ -40,6 +40,6 @@ export const cacheServiceFactory = createServiceFactory({ return CacheManager.fromConfig(config, { logger }); }, async factory({ plugin }, manager) { - return manager.forPlugin(plugin.getId()).getClient(); + return manager.forPlugin(plugin.getId()); }, }); diff --git a/packages/backend-defaults/src/entrypoints/cache/index.ts b/packages/backend-defaults/src/entrypoints/cache/index.ts index b16fa56bd2..958fa36091 100644 --- a/packages/backend-defaults/src/entrypoints/cache/index.ts +++ b/packages/backend-defaults/src/entrypoints/cache/index.ts @@ -16,4 +16,4 @@ export { cacheServiceFactory } from './cacheServiceFactory'; export { CacheManager } from './CacheManager'; -export type { CacheManagerOptions, PluginCacheManager } from './types'; +export type { CacheManagerOptions } from './types'; diff --git a/packages/backend-defaults/src/entrypoints/cache/types.ts b/packages/backend-defaults/src/entrypoints/cache/types.ts index 23c8bcaf3d..7260099a0a 100644 --- a/packages/backend-defaults/src/entrypoints/cache/types.ts +++ b/packages/backend-defaults/src/entrypoints/cache/types.ts @@ -15,10 +15,6 @@ */ import { LoggerService } from '@backstage/backend-plugin-api'; -import { - CacheService, - CacheServiceOptions, -} from '@backstage/backend-plugin-api'; import { HumanDuration, durationToMilliseconds } from '@backstage/types'; /** @@ -39,13 +35,6 @@ export type CacheManagerOptions = { onError?: (err: Error) => void; }; -/** - * @public - */ -export interface PluginCacheManager { - getClient(options?: CacheServiceOptions): CacheService; -} - export function ttlToMilliseconds(ttl: number | HumanDuration): number { return typeof ttl === 'number' ? ttl : durationToMilliseconds(ttl); } diff --git a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts index 0d52ff16ff..26c2d54231 100644 --- a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts +++ b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts @@ -17,20 +17,24 @@ import { ConfigReader } from '@backstage/config'; import { DatabaseManagerImpl } from './DatabaseManager'; import { Connector } from './types'; +import { mockServices } from '@backstage/backend-test-utils'; describe('DatabaseManagerImpl', () => { afterEach(() => { jest.clearAllMocks(); }); + const deps = { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }; + it('calls the right connector, only once per plugin id', async () => { const connector1 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const connector2 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const impl = new DatabaseManagerImpl( @@ -43,30 +47,28 @@ describe('DatabaseManagerImpl', () => { }, ); - await impl.forPlugin('plugin1').getClient(); + await impl.forPlugin('plugin1', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); - await impl.forPlugin('plugin1').getClient(); + await impl.forPlugin('plugin1', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); - await impl.forPlugin('plugin2').getClient(); + await impl.forPlugin('plugin2', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(2); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin2', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin2', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); }); it('respects per-plugin overridden connectors', async () => { const connector1 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const connector2 = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const impl = new DatabaseManagerImpl( @@ -84,22 +86,21 @@ describe('DatabaseManagerImpl', () => { }, ); - await impl.forPlugin('plugin1').getClient(); + await impl.forPlugin('plugin1', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(0); - await impl.forPlugin('plugin2').getClient(); + await impl.forPlugin('plugin2', deps).getClient(); expect(connector1.getClient).toHaveBeenCalledTimes(1); - expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', deps); expect(connector2.getClient).toHaveBeenCalledTimes(1); - expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', undefined); + expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', deps); }); it('migration skip options take precedence over config', async () => { const connector = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const impl = new DatabaseManagerImpl( @@ -117,7 +118,7 @@ describe('DatabaseManagerImpl', () => { }, { migrations: { skip: false } }, ); - expect((await impl.forPlugin('plugin1')).migrations).toEqual({ + expect((await impl.forPlugin('plugin1', deps)).migrations).toEqual({ skip: false, }); @@ -125,7 +126,7 @@ describe('DatabaseManagerImpl', () => { pg: connector, }); - expect((await impl1.forPlugin('plugin1')).migrations).toEqual({ + expect((await impl1.forPlugin('plugin1', deps)).migrations).toEqual({ skip: false, }); }); @@ -133,7 +134,6 @@ describe('DatabaseManagerImpl', () => { it('plugin can skip migrations using config', async () => { const connector = { getClient: jest.fn(), - dropDatabase: jest.fn(), } satisfies Connector; const impl = new DatabaseManagerImpl( @@ -148,10 +148,10 @@ describe('DatabaseManagerImpl', () => { }, ); - expect((await impl.forPlugin('plugin1')).migrations).toEqual({ + expect((await impl.forPlugin('plugin1', deps)).migrations).toEqual({ skip: true, }); - expect((await impl.forPlugin('plugin2')).migrations).toEqual({ + expect((await impl.forPlugin('plugin2', deps)).migrations).toEqual({ skip: false, }); @@ -169,10 +169,10 @@ describe('DatabaseManagerImpl', () => { pg: connector, }, ); - expect((await impl2.forPlugin('plugin1')).migrations).toEqual({ + expect((await impl2.forPlugin('plugin1', deps)).migrations).toEqual({ skip: false, }); - expect((await impl2.forPlugin('plugin2')).migrations).toEqual({ + expect((await impl2.forPlugin('plugin2', deps)).migrations).toEqual({ skip: true, }); }); diff --git a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts index b3e69f0ae2..46d4ee8cf7 100644 --- a/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts +++ b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts @@ -18,7 +18,7 @@ import { DatabaseService, LifecycleService, LoggerService, - PluginMetadataService, + RootConfigService, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { stringifyError } from '@backstage/errors'; @@ -42,21 +42,12 @@ function pluginPath(pluginId: string): string { */ export type DatabaseManagerOptions = { migrations?: DatabaseService['migrations']; - logger?: LoggerService; -}; - -/** - * An interface that represents the legacy global DatabaseManager implementation. - * @public - */ -export type LegacyRootDatabaseService = { - forPlugin(pluginId: string): DatabaseService; }; /** * Testable implementation class for {@link DatabaseManager} below. */ -export class DatabaseManagerImpl implements LegacyRootDatabaseService { +export class DatabaseManagerImpl { constructor( private readonly config: Config, private readonly connectors: Record, @@ -73,9 +64,9 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { */ forPlugin( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): PluginDatabaseManager { const client = this.getClientType(pluginId).client; @@ -135,9 +126,9 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { private async getDatabase( pluginId: string, connector: Connector, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { if (this.databaseCache.has(pluginId)) { @@ -148,13 +139,19 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { this.databaseCache.set(pluginId, clientPromise); if (process.env.NODE_ENV !== 'test') { - clientPromise.then(client => this.startKeepaliveLoop(pluginId, client)); + clientPromise.then(client => + this.startKeepaliveLoop(pluginId, client, deps.logger), + ); } return clientPromise; } - private startKeepaliveLoop(pluginId: string, client: Knex): void { + private startKeepaliveLoop( + pluginId: string, + client: Knex, + logger: LoggerService, + ): void { let lastKeepaliveFailed = false; setInterval(() => { @@ -167,7 +164,7 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { (error: unknown) => { if (!lastKeepaliveFailed) { lastKeepaliveFailed = true; - this.options?.logger?.warn( + logger.warn( `Database keepalive failed for plugin ${pluginId}, ${stringifyError( error, )}`, @@ -192,7 +189,7 @@ export class DatabaseManagerImpl implements LegacyRootDatabaseService { * set `prefix` which is used to prefix generated database names if config is * not provided. */ -export class DatabaseManager implements LegacyRootDatabaseService { +export class DatabaseManager { /** * Creates a {@link DatabaseManager} from `backend.database` config. * @@ -200,7 +197,7 @@ export class DatabaseManager implements LegacyRootDatabaseService { * @param options - An optional configuration object. */ static fromConfig( - config: Config, + config: RootConfigService, options?: DatabaseManagerOptions, ): DatabaseManager { const databaseConfig = config.getConfig('backend.database'); @@ -232,31 +229,11 @@ export class DatabaseManager implements LegacyRootDatabaseService { */ forPlugin( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): PluginDatabaseManager { return this.impl.forPlugin(pluginId, deps); } } - -/** - * Helper for deleting databases. - * - * @public - * @deprecated Will be removed in a future release. - */ -export async function dropDatabase( - dbConfig: Config, - ...databaseNames: string[] -): Promise { - const client = dbConfig.getString('client'); - const prefix = dbConfig.getOptionalString('prefix') || 'backstage_plugin_'; - - if (client === 'pg') { - await new PgConnector(dbConfig, prefix).dropDatabase(...databaseNames); - } else if (client === 'mysql' || client === 'mysql2') { - await new MysqlConnector(dbConfig, prefix).dropDatabase(...databaseNames); - } -} diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts index b11b659a6d..d5bd88deef 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; @@ -253,9 +250,9 @@ export class MysqlConnector implements Connector { async getClient( pluginId: string, - _deps?: { + _deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { const pluginConfig = new ConfigReader( @@ -293,10 +290,6 @@ export class MysqlConnector implements Connector { return client; } - async dropDatabase(...databaseNames: string[]): Promise { - return await dropMysqlDatabase(this.config, ...databaseNames); - } - /** * Provides the canonical database name for a given plugin. * diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index 46b958789d..b5cfffd1f4 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { ForwardedError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; @@ -261,9 +258,9 @@ export class PgConnector implements Connector { async getClient( pluginId: string, - _deps?: { + _deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { const pluginConfig = new ConfigReader( @@ -312,10 +309,6 @@ export class PgConnector implements Connector { return client; } - async dropDatabase(...databaseNames: string[]): Promise { - return await dropPgDatabase(this.config, ...databaseNames); - } - /** * Provides the canonical database name for a given plugin. * diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts index 2bd517d373..769d402eda 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts @@ -20,11 +20,17 @@ import { buildSqliteDatabaseConfig, createSqliteDatabaseClient, } from './sqlite3'; +import { mockServices } from '@backstage/backend-test-utils'; describe('better-sqlite3', () => { const createConfig = (connection: any) => new ConfigReader({ client: 'better-sqlite3', connection }); + const deps = { + logger: mockServices.logger.mock(), + lifecycle: mockServices.lifecycle.mock(), + }; + describe('buildSqliteDatabaseConfig', () => { it('builds an in-memory connection', () => { expect(buildSqliteDatabaseConfig(createConfig(':memory:'))).toEqual({ @@ -90,7 +96,9 @@ describe('better-sqlite3', () => { describe('createSqliteDatabaseClient', () => { it('creates an in memory knex instance', () => { - expect(createSqliteDatabaseClient(createConfig(':memory:'))).toBeTruthy(); + expect( + createSqliteDatabaseClient('p', createConfig(':memory:'), deps), + ).toBeTruthy(); }); }); }); diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts index 97ba6d46fd..67dd9d975f 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts @@ -15,10 +15,7 @@ */ import { DevDataStore } from '@backstage/backend-dev-utils'; -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ensureDirSync } from 'fs-extra'; @@ -35,12 +32,13 @@ import { mergeDatabaseConfig } from './mergeDatabaseConfig'; * @param overrides - Additional options to merge with the config */ export function createSqliteDatabaseClient( + pluginId: string, dbConfig: Config, - overrides?: Knex.Config, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, + overrides?: Knex.Config, ) { const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides); const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig; @@ -62,7 +60,7 @@ export function createSqliteDatabaseClient( const devStore = DevDataStore.get(); if (devStore) { - const dataKey = `sqlite3-db-${deps.pluginMetadata.getId()}`; + const dataKey = `sqlite3-db-${pluginId}`; const connectionLoader = async () => { // If seed data is available, use it tconnectionLoader restore the database @@ -180,9 +178,9 @@ export class Sqlite3Connector implements Connector { async getClient( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise { const pluginConfig = new ConfigReader( @@ -202,18 +200,15 @@ export class Sqlite3Connector implements Connector { ); const client = createSqliteDatabaseClient( + pluginId, pluginConfig, - databaseClientOverrides, deps, + databaseClientOverrides, ); return client; } - async dropDatabase(..._databaseNames: string[]): Promise { - // do nothing - } - /** * Provides the canonical database name for a given plugin. * diff --git a/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts b/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts index 3bdf1790f3..167779e807 100644 --- a/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts @@ -35,6 +35,7 @@ export const databaseServiceFactory = createServiceFactory({ deps: { config: coreServices.rootConfig, lifecycle: coreServices.lifecycle, + logger: coreServices.logger, pluginMetadata: coreServices.pluginMetadata, }, async createRootContext({ config }) { @@ -48,10 +49,10 @@ export const databaseServiceFactory = createServiceFactory({ }), ); }, - async factory({ pluginMetadata, lifecycle }, databaseManager) { + async factory({ pluginMetadata, lifecycle, logger }, databaseManager) { return databaseManager.forPlugin(pluginMetadata.getId(), { - pluginMetadata, lifecycle, + logger, }); }, }); diff --git a/packages/backend-defaults/src/entrypoints/database/index.ts b/packages/backend-defaults/src/entrypoints/database/index.ts index 7d6221b856..e30d06cbdd 100644 --- a/packages/backend-defaults/src/entrypoints/database/index.ts +++ b/packages/backend-defaults/src/entrypoints/database/index.ts @@ -18,6 +18,4 @@ export { databaseServiceFactory } from './databaseServiceFactory'; export { DatabaseManager, type DatabaseManagerOptions, - type LegacyRootDatabaseService, - dropDatabase, } from './DatabaseManager'; diff --git a/packages/backend-defaults/src/entrypoints/database/types.ts b/packages/backend-defaults/src/entrypoints/database/types.ts index a9cceaa1f9..beded85d65 100644 --- a/packages/backend-defaults/src/entrypoints/database/types.ts +++ b/packages/backend-defaults/src/entrypoints/database/types.ts @@ -14,87 +14,17 @@ * limitations under the License. */ -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import { LifecycleService, LoggerService } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; export type { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; -/** - * Manages an underlying Knex database driver. - */ -export interface DatabaseConnector { - /** - * Provides an instance of a knex database connector. - */ - createClient( - dbConfig: Config, - overrides?: Partial, - deps?: { - lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; - }, - ): Knex; - - /** - * Provides a partial knex config sufficient to override a database name. - */ - createNameOverride(name: string): Partial; - - /** - * Provides a partial knex config sufficient to override a PostgreSQL schema - * name within utilizing the `searchPath` knex configuration. - */ - createSchemaOverride?(name: string): Partial; - - /** - * Produces a knex connection config object representing a database connection - * string. - */ - parseConnectionString( - connectionString: string, - client?: string, - ): Knex.StaticConnectionConfig; - - /** - * Performs a side-effect to ensure database names passed in are present. - * - * Calling this function on databases which already exist should do nothing. - * Missing databases should be created if needed. - */ - ensureDatabaseExists?( - dbConfig: Config, - ...databases: Array - ): Promise; - - /** - * Performs a side-effect to ensure schema names passed in are present. - * - * Calling this function on schemas which already exist should do nothing. - * Missing schemas should be created if needed. - */ - ensureSchemaExists?( - dbConfig: Config, - ...schemas: Array - ): Promise; - - /** - * Deletes databases. - */ - dropDatabase?(dbConfig: Config, ...databases: Array): Promise; -} - export interface Connector { getClient( pluginId: string, - deps?: { + deps: { + logger: LoggerService; lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; }, ): Promise; - - dropDatabase(...databaseNames: string[]): Promise; } diff --git a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts index 4e6aff5853..e4e30044dd 100644 --- a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts +++ b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts @@ -54,25 +54,6 @@ describe('HostDiscovery', () => { ); }); - it('can configure the base path', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - }), - { basePath: '/service' }, - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://localhost:80/service/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://localhost:40/service/catalog', - ); - }); - it.each([ [{ listen: ':80' }, 'http://localhost:80'], [{ listen: ':40', https: true }, 'https://localhost:40'], diff --git a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts index 559dda2aa0..47793c82f7 100644 --- a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts +++ b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts @@ -15,7 +15,10 @@ */ import { Config } from '@backstage/config'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; +import { + DiscoveryService, + RootConfigService, +} from '@backstage/backend-plugin-api'; import { readHttpServerOptions } from '../rootHttpRouter/http/config'; type Target = string | { internal: string; external: string }; @@ -39,6 +42,7 @@ export class HostDiscovery implements DiscoveryService { * * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`. * eg. + * * ```yaml * discovery: * endpoints: @@ -52,11 +56,11 @@ export class HostDiscovery implements DiscoveryService { * plugins: [search] * ``` * - * The basePath defaults to `/api`, meaning the default full internal + * The fixed base path is `/api`, meaning the default full internal * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`. */ - static fromConfig(config: Config, options?: { basePath?: string }) { - const basePath = options?.basePath ?? '/api'; + static fromConfig(config: RootConfigService) { + const basePath = '/api'; const externalBaseUrl = config .getString('backend.baseUrl') .replace(/\/+$/, ''); diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.ts b/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.ts index a69fa5a804..dd6c95ca82 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/createCredentialsBarrier.ts @@ -27,7 +27,7 @@ export function createPathPolicyPredicate(policyPath: string) { return () => true; } - const pathRegex = pathToRegexp(policyPath, undefined, { + const { regexp: pathRegex } = pathToRegexp(policyPath, { end: false, }); diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.ts b/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.ts index 3f8ee3ea69..18ff2f0840 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware.ts @@ -23,7 +23,7 @@ export const DEFAULT_TIMEOUT = { seconds: 5 }; /** * Options for {@link createLifecycleMiddleware}. - * @public + * @internal */ export interface LifecycleMiddlewareOptions { lifecycle: LifecycleService; @@ -47,7 +47,7 @@ export interface LifecycleMiddlewareOptions { * If the service is shutting down, all requests will be rejected with a * {@link @backstage/errors#ServiceUnavailableError}. * - * @public + * @internal */ export function createLifecycleMiddleware( options: LifecycleMiddlewareOptions, diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/index.ts b/packages/backend-defaults/src/entrypoints/httpRouter/index.ts index bc12480ae8..15bced5ef3 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/index.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/index.ts @@ -15,5 +15,3 @@ */ export { httpRouterServiceFactory } from './httpRouterServiceFactory'; -export { createLifecycleMiddleware } from './createLifecycleMiddleware'; -export type { LifecycleMiddlewareOptions } from './createLifecycleMiddleware'; diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts index cb496dd702..96e9ed6509 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/UrlReaders.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import { + LoggerService, + RootConfigService, + UrlReaderService, +} from '@backstage/backend-plugin-api'; import { ReaderFactory } from './types'; import { UrlReaderPredicateMux } from './UrlReaderPredicateMux'; import { AzureUrlReader } from './AzureUrlReader'; @@ -40,7 +43,7 @@ import { HarnessUrlReader } from './HarnessUrlReader'; */ export type UrlReadersOptions = { /** Root config object */ - config: Config; + config: RootConfigService; /** Logger used by all the readers */ logger: LoggerService; /** A list of factories used to construct individual readers that match on URLs */ diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts index 510b12545f..5afbed178f 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/types.ts @@ -15,11 +15,11 @@ */ import { Readable } from 'stream'; -import { Config } from '@backstage/config'; import { UrlReaderService, UrlReaderServiceReadTreeResponse, LoggerService, + RootConfigService, } from '@backstage/backend-plugin-api'; /** @@ -40,7 +40,7 @@ export type UrlReaderPredicateTuple = { * @public */ export type ReaderFactory = (options: { - config: Config; + config: RootConfigService; logger: LoggerService; treeResponseFactory: ReadTreeResponseFactory; }) => UrlReaderPredicateTuple[]; diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 902f805fbd..327aeebf54 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/backend-dynamic-feature-service +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@1.0.0-next.2 + - @backstage/backend-common@0.25.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/cli-node@0.2.8-next.0 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/plugin-events-backend@0.3.12-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.2 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/backend-app-api@0.10.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-scaffolder-node@0.4.11-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + ## 0.4.0-next.0 ### Minor Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 20ef7790f6..a6d3ccb8b7 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dynamic-feature-service", - "version": "0.4.0-next.0", + "version": "0.4.0-next.2", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index df3dce4cf6..c2b639b46b 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,89 @@ # example-backend-legacy +## 0.2.102-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.2 + - @backstage/plugin-devtools-backend@0.4.0-next.2 + - @backstage/plugin-scaffolder-backend@1.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/catalog-client@1.7.0-next.1 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/plugin-app-backend@0.3.74-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/plugin-events-backend@0.3.12-next.2 + - @backstage/plugin-kubernetes-backend@0.18.6-next.2 + - @backstage/plugin-permission-backend@0.5.49-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/plugin-search-backend@1.5.17-next.2 + - @backstage/plugin-signals-backend@0.2.0-next.2 + - @backstage/plugin-techdocs-backend@1.10.13-next.2 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-proxy-backend@0.5.6-next.2 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.2 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-signals-node@0.1.11-next.2 + +## 0.2.102-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-events-backend@0.3.12-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.6-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.35-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + ## 0.2.102-next.0 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index 8d2eb61566..5a19677e99 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.102-next.0", + "version": "0.2.102-next.2", "backstage": { "role": "backend" }, diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 1393b93790..8540bbba8f 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/backend-openapi-utils +## 0.1.18-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/errors@1.2.4 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/errors@1.2.4 + ## 0.1.18-next.0 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 2ae545af9f..fd96f46d2b 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-openapi-utils", "description": "OpenAPI typescript support.", - "version": "0.1.18-next.0", + "version": "0.1.18-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "files": [ "dist" diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 2356b1714d..0f5abfdb2f 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/backend-plugin-api +## 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 https://github.com/backstage/backstage/issues/26353 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 + +- 8052b9b: Add a `toJSON` on refs so that they can appear in expectations in jest tests +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + +## 0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + ## 0.9.0-next.0 ### Minor Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 6a3392529d..a2ab753ca2 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-plugin-api", - "version": "0.9.0-next.0", + "version": "1.0.0-next.2", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index a14e388bd0..7e4b78b5a1 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -17,7 +17,9 @@ import { BackendFeature } from '../../types'; /** - * TODO + * A reference to a backend service. You can use these references to mark + * dependencies on services and having their implementations injected + * automatically. * * @public */ @@ -39,6 +41,11 @@ export type ServiceRef< */ scope: TScope; + /** + * Marks whether the service is a multiton or not. Multiton services the + * opposite of singletons - they can be provided many times, and when depended + * on, you receive an array of all provided instances. + */ multiton?: TInstances extends 'multiton' ? true : false; /** @@ -142,6 +149,15 @@ export function createServiceRef< toString() { return `serviceRef{${options.id}}`; }, + toJSON() { + // This avoids accidental calls to T happening e.g. in tests + return { + $$type: '@backstage/ServiceRef', + id, + scope, + multiton, + }; + }, $$type: '@backstage/ServiceRef', __defaultFactory: defaultFactory, } as ServiceRef & { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 49a2c44ddf..409e2af0b8 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/backend-test-utils +## 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 https://github.com/backstage/backstage/issues/26353 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 diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 680a7b9c2c..a15716b61a 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-test-utils", - "version": "0.6.0-next.0", + "version": "1.0.0-next.2", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" @@ -77,7 +77,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "peerDependencies": { "@types/jest": "*" diff --git a/packages/backend-test-utils/src/database/mysql.ts b/packages/backend-test-utils/src/database/mysql.ts index ab130dc5c8..b27edb7f9d 100644 --- a/packages/backend-test-utils/src/database/mysql.ts +++ b/packages/backend-test-utils/src/database/mysql.ts @@ -235,7 +235,11 @@ export class MysqlEngine implements Engine { client: this.#properties.driver, connection, pool: { - acquireTimeoutMillis: 10000, + min: 0, + max: 1, + acquireTimeoutMillis: 20_000, + createTimeoutMillis: 20_000, + createRetryIntervalMillis: 1_000, }, }); } diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts index 7bf3887c93..812e234828 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -334,4 +334,78 @@ describe('TestBackend', () => { "Unable to determine the plugin ID of extension point(s) 'a'. Tested extension points must be depended on by one or more tested modules.", ); }); + + it('should forward errors from plugins', async () => { + await expect( + startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw new Error('nah'); + }, + }); + }, + }), + ], + }), + ).rejects.toThrow("Plugin 'test' startup failed; caused by Error: nah"); + }); + + it('should forward errors from modules', async () => { + await expect( + startTestBackend({ + features: [ + createBackendModule({ + pluginId: 'test', + moduleId: 'tester', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw new Error('nah'); + }, + }); + }, + }), + ], + }), + ).rejects.toThrow( + "Module 'tester' for plugin 'test' startup failed; caused by Error: nah", + ); + }); + + it('should forward errors from plugin register', async () => { + await expect( + startTestBackend({ + features: [ + createBackendPlugin({ + pluginId: 'test', + register() { + throw new Error('nah'); + }, + }), + ], + }), + ).rejects.toThrow('nah'); + }); + + it('should forward errors from module register', async () => { + await expect( + startTestBackend({ + features: [ + createBackendModule({ + pluginId: 'test', + moduleId: 'tester', + register() { + throw new Error('nah'); + }, + }), + ], + }), + ).rejects.toThrow('nah'); + }); }); diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index ccb3825cd2..d7e2f0b706 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,77 @@ # example-backend +## 0.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-devtools-backend@0.4.0-next.2 + - @backstage/plugin-scaffolder-backend@1.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/plugin-catalog-backend@1.26.0-next.2 + - @backstage/plugin-app-backend@0.3.74-next.2 + - @backstage/plugin-notifications-backend@0.4.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.2 + - @backstage/plugin-kubernetes-backend@0.18.6-next.2 + - @backstage/plugin-permission-backend@0.5.49-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/plugin-search-backend@1.5.17-next.2 + - @backstage/plugin-signals-backend@0.2.0-next.2 + - @backstage/plugin-techdocs-backend@1.10.13-next.2 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-proxy-backend@0.5.6-next.2 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.2 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + +## 0.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/plugin-auth-node@0.5.2-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/plugin-scaffolder-backend@1.25.0-next.1 + - @backstage/plugin-notifications-backend@0.4.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.6-next.1 + - @backstage/plugin-techdocs-backend@1.10.13-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-app-backend@0.3.74-next.1 + - @backstage/plugin-auth-backend@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.0-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.0-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.5.0-next.1 + - @backstage/plugin-devtools-backend@0.4.0-next.1 + - @backstage/plugin-permission-backend@0.5.49-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-proxy-backend@0.5.6-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.5.0-next.1 + - @backstage/plugin-search-backend@1.5.17-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.2.2-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.2-next.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-signals-backend@0.2.0-next.1 + ## 0.0.30-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 4319728c04..bb427263bd 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.30-next.0", + "version": "0.0.30-next.2", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -57,9 +57,9 @@ "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", "@backstage/plugin-techdocs-backend": "workspace:^", - "@opentelemetry/auto-instrumentations-node": "^0.49.0", - "@opentelemetry/exporter-prometheus": "^0.50.0", - "@opentelemetry/sdk-node": "^0.50.0", + "@opentelemetry/auto-instrumentations-node": "^0.50.0", + "@opentelemetry/exporter-prometheus": "^0.53.0", + "@opentelemetry/sdk-node": "^0.53.0", "example-app": "link:../app" }, "devDependencies": { diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 9ed8a50b91..9abc5050e4 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/catalog-client +## 1.7.0-next.1 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 1.6.7-next.0 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + ## 1.6.6 ### Patch Changes diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index ce25990fd0..203028edce 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -269,6 +269,7 @@ export type QueryEntitiesCursorRequest = { export type QueryEntitiesInitialRequest = { fields?: string[]; limit?: number; + offset?: number; filter?: EntityFilterQuery; orderFields?: EntityOrderQuery; fullTextFilter?: { diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 29c1d3049c..d09a85efbc 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.6.6", + "version": "1.7.0-next.1", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 2e7912b69a..4de314c1e8 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -191,6 +191,7 @@ export class CatalogClient implements CatalogApi { fields = [], filter, limit, + offset, orderFields, fullTextFilter, } = request; @@ -199,6 +200,9 @@ export class CatalogClient implements CatalogApi { if (limit !== undefined) { params.limit = limit; } + if (offset !== undefined) { + params.offset = offset; + } if (orderFields !== undefined) { params.orderField = ( Array.isArray(orderFields) ? orderFields : [orderFields] diff --git a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts index 7619e69504..1f1c54d6f8 100644 --- a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts +++ b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts @@ -247,6 +247,7 @@ export class DefaultApiClient { query: { fields?: Array; limit?: number; + offset?: number; orderField?: Array; cursor?: string; filter?: Array; @@ -258,7 +259,7 @@ export class DefaultApiClient { ): Promise> { const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); - const uriTemplate = `/entities/by-query{?fields,limit,orderField*,cursor,filter*,fullTextFilterTerm,fullTextFilterFields}`; + const uriTemplate = `/entities/by-query{?fields,limit,offset,orderField*,cursor,filter*,fullTextFilterTerm,fullTextFilterFields}`; const uri = parser.parse(uriTemplate).expand({ ...request.query, diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index 7eded50776..c8fbc7e7ac 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -412,6 +412,7 @@ export type QueryEntitiesRequest = export type QueryEntitiesInitialRequest = { fields?: string[]; limit?: number; + offset?: number; filter?: EntityFilterQuery; orderFields?: EntityOrderQuery; fullTextFilter?: { diff --git a/packages/catalog-client/src/utils.ts b/packages/catalog-client/src/utils.ts index de21cea0d4..9d51c34e71 100644 --- a/packages/catalog-client/src/utils.ts +++ b/packages/catalog-client/src/utils.ts @@ -17,7 +17,6 @@ import { QueryEntitiesCursorRequest, QueryEntitiesInitialRequest, - QueryEntitiesRequest, } from './types/api'; export function isQueryEntitiesInitialRequest( @@ -25,9 +24,3 @@ export function isQueryEntitiesInitialRequest( ): request is QueryEntitiesInitialRequest { return !(request as QueryEntitiesCursorRequest).cursor; } - -export function isQueryEntitiesCursorRequest( - request: QueryEntitiesRequest, -): request is QueryEntitiesCursorRequest { - return !isQueryEntitiesInitialRequest(request); -} diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md index 21995e4812..1dcdf77b2c 100644 --- a/packages/cli-node/CHANGELOG.md +++ b/packages/cli-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/cli-node +## 0.2.8-next.0 + +### Patch Changes + +- 0c70f43: Add definition for the new `backstage.inline` field in `package.json`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.7 ### Patch Changes diff --git a/packages/cli-node/api-report.md b/packages/cli-node/api-report.md index 9f7e525048..f22583dd25 100644 --- a/packages/cli-node/api-report.md +++ b/packages/cli-node/api-report.md @@ -18,6 +18,7 @@ export interface BackstagePackageJson { backstage?: { role?: PackageRole; moved?: string; + inline?: boolean; pluginId?: string | null; pluginPackage?: string; pluginPackages?: string[]; diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index c727918510..98f8b67df7 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-node", - "version": "0.2.7", + "version": "0.2.8-next.0", "description": "Node.js library for Backstage CLIs", "backstage": { "role": "node-library" diff --git a/packages/cli-node/src/monorepo/PackageGraph.ts b/packages/cli-node/src/monorepo/PackageGraph.ts index c8877803d5..69ec99c2dd 100644 --- a/packages/cli-node/src/monorepo/PackageGraph.ts +++ b/packages/cli-node/src/monorepo/PackageGraph.ts @@ -47,6 +47,15 @@ export interface BackstagePackageJson { role?: PackageRole; moved?: string; + /** + * If set to `true`, the package will be treated as an internal package + * where any imports will be inlined into the consuming package. + * + * When set to `true`, the top-level `private` field must be set to `true` + * as well. + */ + inline?: boolean; + /** * The ID of the plugin if this is a plugin package. Must always be set for plugin and module packages, and may be set for library packages. A `null` value means that the package is explicitly not a plugin package. */ diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c2a17a5b93..76d8c997e2 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/cli +## 0.27.1-next.2 + +### Patch Changes + +- 16ffdd6: Remove direct `vite` dependency +- 8069f4a: Update Scaffolder module template to add itself to the backend +- 0e1a817: The app build process now outputs an additional `index.html.tmpl` file. This is an non-templated version of the `index.html` file, which can be used to delay templating until runtime. + + The new `index.html.tmpl` file also sets a `backstage-public-path` meta tag to be templated at runtime. The meta tag is in turn picked up by the new `@backstage/cli/config/webpack-public-path.js` entry point script, which uses it to set the runtime public path of the Webpack bundle. + +- d29fc1b: Updated dependency `@module-federation/enhanced` to `^0.6.0`. +- 4ebf36f: Upgrade to `vite@v5` +- 2d3caaf: The build commands now support the new `backstage.inline` flag in `package.json`, which causes the contents of private packages to be inlined into the consuming package, rather than be treated as an external dependency. +- 3d88455: Add support for `backstage:^` version ranges to versions:bump when using the experimental yarn plugin +- Updated dependencies + - @backstage/cli-node@0.2.8-next.0 + - @backstage/integration@1.15.0-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/eslint-plugin@0.1.9-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.27.1-next.1 + +### Patch Changes + +- d2d2313: Add `config.d.ts` files to the list of included file in `tsconfig.json`. + + This allows ESLint to detect issues or deprecations in those files. + +- 97422b0: Update templates to not refer to backend-common +- f865103: Updated dependency `esbuild` to `^0.23.0`. +- 569c3f0: Fixed an issue where published frontend packages would end up with an invalid import structure if a single module imported both `.css` and `.svg` files. +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + ## 0.27.1-next.0 ### Patch Changes diff --git a/packages/cli/config/webpack-public-path.js b/packages/cli/config/webpack-public-path.js new file mode 100644 index 0000000000..3e14e96eb9 --- /dev/null +++ b/packages/cli/config/webpack-public-path.js @@ -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. + */ + +// This script is used to pick up and set the public path of the Webpack bundle +// at runtime. The meta tag is injected by the app build, but only present in +// the `index.html.tmpl` file. The runtime value of the meta tag is populated by +// the app backend, when it templates the final `index.html` file. +// +// This is needed for additional chunks to use the correct public path, and it +// is not possible to set the `__webpack_public_path__` variable outside of the +// build itself. The Webpack output also does not read any tags or +// similar, this seems to be the only way to dynamically configure the public +// path at runtime. +const el = document.querySelector('meta[name="backstage-public-path"]'); +const path = el?.getAttribute('content'); +if (path) { + __webpack_public_path__ = path; +} diff --git a/packages/cli/package.json b/packages/cli/package.json index 89230c13b2..31ed75360c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.27.1-next.0", + "version": "0.27.1-next.2", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" @@ -53,7 +53,7 @@ "@backstage/release-manifests": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@module-federation/enhanced": "^0.3.5", + "@module-federation/enhanced": "^0.6.0", "@octokit/graphql": "^5.0.0", "@octokit/graphql-schema": "^13.7.0", "@octokit/oauth-app": "^4.2.0", @@ -130,6 +130,7 @@ "pirates": "^4.0.6", "postcss": "^8.1.0", "process": "^0.11.10", + "raw-loader": "^4.0.2", "react-dev-utils": "^12.0.0-next.60", "react-refresh": "^0.14.0", "recursive-readdir": "^2.2.2", @@ -182,18 +183,18 @@ "@types/tar": "^6.1.1", "@types/terser-webpack-plugin": "^5.0.4", "@types/yarnpkg__lockfile": "^1.1.4", - "@vitejs/plugin-react": "^4.0.4", + "@vitejs/plugin-react": "^4.3.1", "del": "^7.0.0", "msw": "^1.0.0", "nodemon": "^3.0.1", - "vite": "^4.4.9", - "vite-plugin-html": "^3.2.0", + "vite": "^5.0.0", + "vite-plugin-html": "^3.2.2", "vite-plugin-node-polyfills": "^0.22.0" }, "peerDependencies": { - "@vitejs/plugin-react": "^4.0.4", - "vite": "^4.4.9", - "vite-plugin-html": "^3.2.0", + "@vitejs/plugin-react": "^4.3.1", + "vite": "^5.0.0", + "vite-plugin-html": "^3.2.2", "vite-plugin-node-polyfills": "^0.22.0" }, "peerDependenciesMeta": { diff --git a/packages/cli/src/commands/build/buildBackend.ts b/packages/cli/src/commands/build/buildBackend.ts index 3609e40eb5..88d322d482 100644 --- a/packages/cli/src/commands/build/buildBackend.ts +++ b/packages/cli/src/commands/build/buildBackend.ts @@ -21,6 +21,7 @@ import tar, { CreateOptions } from 'tar'; import { createDistWorkspace } from '../../lib/packager'; import { getEnvironmentParallelism } from '../../lib/parallel'; import { buildPackage, Output } from '../../lib/builder'; +import { PackageGraph } from '@backstage/cli-node'; const BUNDLE_FILE = 'bundle.tar.gz'; const SKELETON_FILE = 'skeleton.tar.gz'; @@ -42,6 +43,7 @@ export async function buildBackend(options: BuildBackendOptions) { packageJson: pkg, outputs: new Set([Output.cjs]), minify, + workspacePackages: await PackageGraph.listTargetPackages(), }); const tmpDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-bundle')); diff --git a/packages/cli/src/commands/build/command.ts b/packages/cli/src/commands/build/command.ts index c0ad3c912b..a5f7f60971 100644 --- a/packages/cli/src/commands/build/command.ts +++ b/packages/cli/src/commands/build/command.ts @@ -17,7 +17,7 @@ import { OptionValues } from 'commander'; import { buildPackage, Output } from '../../lib/builder'; import { findRoleFromCommand } from '../../lib/role'; -import { PackageRoles } from '@backstage/cli-node'; +import { PackageGraph, PackageRoles } from '@backstage/cli-node'; import { paths } from '../../lib/paths'; import { buildFrontend } from './buildFrontend'; import { buildBackend } from './buildBackend'; @@ -82,5 +82,6 @@ export async function command(opts: OptionValues): Promise { return buildPackage({ outputs, minify: Boolean(opts.minify), + workspacePackages: await PackageGraph.listTargetPackages(), }); } diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index 6d362c7d98..6a2b9be06d 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -137,6 +137,7 @@ export async function command(opts: OptionValues, cmd: Command): Promise { outputs, logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `, minify: buildOptions.minify, + workspacePackages: packages, }; }); diff --git a/packages/cli/src/commands/repo/list-deprecations.ts b/packages/cli/src/commands/repo/list-deprecations.ts index 69fe51f78e..772038025a 100644 --- a/packages/cli/src/commands/repo/list-deprecations.ts +++ b/packages/cli/src/commands/repo/list-deprecations.ts @@ -17,7 +17,7 @@ import chalk from 'chalk'; import { ESLint } from 'eslint'; import { OptionValues } from 'commander'; -import { join as joinPath, relative as relativePath } from 'path'; +import { relative as relativePath } from 'path'; import { PackageGraph } from '@backstage/cli-node'; import { paths } from '../../lib/paths'; @@ -45,7 +45,7 @@ export async function command(opts: OptionValues) { const deprecations = []; for (const [index, pkg] of packages.entries()) { - const results = await eslint.lintFiles(joinPath(pkg.dir, 'src')); + const results = await eslint.lintFiles(pkg.dir); for (const result of results) { for (const message of result.messages) { if (message.ruleId !== 'deprecation/deprecation') { diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 5b981d3890..e41bd98c49 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -25,6 +25,8 @@ import chalk from 'chalk'; import ora from 'ora'; import semver from 'semver'; import { OptionValues } from 'commander'; +import yaml from 'yaml'; +import z from 'zod'; import { isError, NotFoundError } from '@backstage/errors'; import { resolve as resolvePath } from 'path'; import { run } from '../../lib/run'; @@ -76,6 +78,8 @@ type PkgVersionInfo = { export default async (opts: OptionValues) => { const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); + const hasYarnPlugin = await getHasYarnPlugin(); + let pattern = opts.pattern; if (!pattern) { @@ -177,12 +181,34 @@ export default async (opts: OptionValues) => { for (const depType of DEP_TYPES) { if (depType in pkgJson && dep.name in pkgJson[depType]) { const oldRange = pkgJson[depType][dep.name]; - pkgJson[depType][dep.name] = dep.range; + + // backstage:^ are written to the lockfile as + // backstage:, so that updates to + // backstage.json can be detected during yarn install. In order to + // locate the corresponding lockfile entry for "backstage:^" + // versions, we need to perform the same transformation. + const oldLockfileRange = await asLockfileVersion(oldRange); + + const useBackstageRange = + hasYarnPlugin && + // Only use backstage:^ versions if the package is present in + // the manifest for the release we're bumping to. + releaseManifest.packages.find( + ({ name: manifestPackageName }) => + dep.name === manifestPackageName, + ) && + // Don't use backstage:^ versions for peerDependencies; they only + // support npm and workspace: versions. + depType !== 'peerDependencies'; + + const newRange = useBackstageRange ? 'backstage:^' : dep.range; + + pkgJson[depType][dep.name] = newRange; // Check if the update was at least a pre-v1 minor or post-v1 major release const lockfileEntry = lockfile .get(dep.name) - ?.find(entry => entry.range === oldRange); + ?.find(entry => entry.range === oldLockfileRange); if (lockfileEntry) { const from = lockfileEntry.version; const to = dep.target; @@ -266,6 +292,21 @@ export default async (opts: OptionValues) => { console.log(); } + if (hasYarnPlugin) { + console.log(); + console.log( + chalk.blue( + `${chalk.bold( + 'NOTE', + )}: this bump used backstage:^ versions in package.json files, since the Backstage ` + + `yarn plugin was detected in the repository. To migrate back to explicit npm versions, ` + + `remove the plugin by running "yarn plugin remove @yarnpkg/plugin-backstage", then ` + + `repeat this command.`, + ), + ); + console.log(); + } + console.log(chalk.green('Version bump complete!')); } @@ -354,16 +395,23 @@ export function createVersionFinder(options: { }; } -export async function bumpBackstageJsonVersion(version: string) { - const backstageJsonPath = paths.resolveTargetRoot(BACKSTAGE_JSON); - const backstageJson = await fs.readJSON(backstageJsonPath).catch(e => { +function getBackstageJsonPath() { + return paths.resolveTargetRoot(BACKSTAGE_JSON); +} + +async function getBackstageJson() { + const backstageJsonPath = getBackstageJsonPath(); + return fs.readJSON(backstageJsonPath).catch(e => { if (e.code === 'ENOENT') { // gracefully continue in case the file doesn't exist return; } throw e; }); +} +export async function bumpBackstageJsonVersion(version: string) { + const backstageJson = await getBackstageJson(); const prevVersion = backstageJson?.version; if (prevVersion === version) { @@ -394,7 +442,7 @@ export async function bumpBackstageJsonVersion(version: string) { } await fs.writeJson( - backstageJsonPath, + getBackstageJsonPath(), { ...backstageJson, version }, { spaces: 2, @@ -403,6 +451,53 @@ export async function bumpBackstageJsonVersion(version: string) { ); } +async function asLockfileVersion(version: string) { + if (version === 'backstage:^') { + return `backstage:${(await getBackstageJson())?.version}`; + } + + return version; +} + +const yarnRcSchema = z.object({ + plugins: z + .array( + z.object({ + path: z.string(), + }), + ) + .optional(), +}); + +async function getHasYarnPlugin() { + const yarnRcPath = paths.resolveTargetRoot('.yarnrc.yml'); + const yarnRcContent = await fs.readFile(yarnRcPath, 'utf-8').catch(e => { + if (e.code === 'ENOENT') { + // gracefully continue in case the file doesn't exist + return ''; + } + throw e; + }); + + if (!yarnRcContent) { + return false; + } + + const parseResult = yarnRcSchema.safeParse(yaml.parse(yarnRcContent)); + + if (!parseResult.success) { + throw new Error( + `Unexpected content in .yarnrc.yml: ${parseResult.error.toString()}`, + ); + } + + const yarnRc = parseResult.data; + + return yarnRc.plugins?.some( + plugin => plugin.path === '.yarn/plugins/@yarnpkg/plugin-backstage.cjs', + ); +} + export async function runYarnInstall() { const spinner = ora({ prefixText: `Running ${chalk.blue('yarn install')} to install new versions`, diff --git a/packages/cli/src/lib/builder/config.test.ts b/packages/cli/src/lib/builder/config.test.ts index 733c35fb55..e426e1ffa5 100644 --- a/packages/cli/src/lib/builder/config.test.ts +++ b/packages/cli/src/lib/builder/config.test.ts @@ -29,6 +29,7 @@ describe('makeRollupConfigs', () => { version: '0.0.0', main: './src/index.ts', }, + workspacePackages: [], }); const external = config.external as Exclude< ExternalOption, diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 8beb2cbb1e..a11ffe8845 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -53,6 +53,21 @@ function isFileImport(source: string) { return false; } +function buildInternalImportPattern(options: BuildOptions) { + const inlinedPackages = options.workspacePackages.filter( + pkg => pkg.packageJson.backstage?.inline, + ); + for (const { packageJson } of inlinedPackages) { + if (!packageJson.private) { + throw new Error( + `Inlined package ${packageJson.name} must be marked as private`, + ); + } + } + const names = inlinedPackages.map(pkg => pkg.packageJson.name); + return new RegExp(`^(?:${names.join('|')})(?:$|/)`); +} + export async function makeRollupConfigs( options: BuildOptions, ): Promise { @@ -83,6 +98,19 @@ export async function makeRollupConfigs( SCRIPT_EXTS.includes(e.ext), ); + const internalImportPattern = buildInternalImportPattern(options); + const external = ( + source: string, + importer: string | undefined, + isResolved: boolean, + ) => + Boolean( + importer && + !isResolved && + !internalImportPattern.test(source) && + !isFileImport(source), + ); + if (options.outputs.has(Output.cjs) || options.outputs.has(Output.esm)) { const output = new Array(); const mainFields = ['module', 'main']; @@ -118,10 +146,10 @@ export async function makeRollupConfigs( ), output, onwarn, + makeAbsoluteExternalsRelative: false, preserveEntrySignatures: 'strict', // All module imports are always marked as external - external: (source, importer, isResolved) => - Boolean(importer && !isResolved && !isFileImport(source)), + external, plugins: [ resolve({ mainFields }), commonjs({ @@ -190,18 +218,11 @@ export async function makeRollupConfigs( chunkFileNames: `types/[name]-[hash].d.ts`, format: 'es', }, - external: [ - /\.css$/, - /\.scss$/, - /\.sass$/, - /\.svg$/, - /\.eot$/, - /\.woff$/, - /\.woff2$/, - /\.ttf$/, - ], + external: (source, importer, isResolved) => + /\.css|scss|sass|svg|eot|woff|woff2|ttf$/.test(source) || + external(source, importer, isResolved), onwarn, - plugins: [dts()], + plugins: [dts({ respectExternal: true })], }); } diff --git a/packages/cli/src/lib/builder/types.ts b/packages/cli/src/lib/builder/types.ts index 7d9c34fe29..937bd157c9 100644 --- a/packages/cli/src/lib/builder/types.ts +++ b/packages/cli/src/lib/builder/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BackstagePackageJson } from '@backstage/cli-node'; +import { BackstagePackage, BackstagePackageJson } from '@backstage/cli-node'; export enum Output { esm, @@ -28,4 +28,5 @@ export type BuildOptions = { packageJson?: BackstagePackageJson; outputs: Set; minify?: boolean; + workspacePackages: BackstagePackage[]; }; diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index da7b378fbd..a64bc0fcd8 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -198,6 +198,19 @@ export async function createConfig( }, }), ); + plugins.push( + new HtmlWebpackPlugin({ + meta: { + 'backstage-app-mode': options?.appMode ?? 'public', + // This is added to be written in the later step, and finally read by the extra entry point + 'backstage-public-path': '<%= publicPath %>/', + }, + minify: false, + publicPath: '<%= publicPath %>', + filename: 'index.html.tmpl', + template: `raw-loader!${paths.targetHtml}`, + }), + ); } if (options.moduleFederation) { @@ -341,7 +354,11 @@ export async function createConfig( }, devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map', context: paths.targetPath, - entry: [...(options.additionalEntryPoints ?? []), paths.targetEntry], + entry: [ + require.resolve('@backstage/cli/config/webpack-public-path'), + ...(options.additionalEntryPoints ?? []), + paths.targetEntry, + ], resolve: { extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx', '.json', '.wasm'], mainFields: ['browser', 'module', 'main'], diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 259f3263eb..d73bcc662e 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -54,6 +54,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be const { name } = await fs.readJson(libPaths.resolveTarget('package.json')); let webpackServer: WebpackDevServer | undefined = undefined; + // @ts-ignore let viteServer: import('vite').ViteDevServer | undefined = undefined; let latestFrontendAppConfigs: AppConfig[] = []; diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index 302b2fbe68..17a6a0c191 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ModuleOptions, WebpackPluginInstance } from 'webpack'; +import { RuleSetRule, WebpackPluginInstance } from 'webpack'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import { svgrTemplate } from '../svgrTemplate'; type Transforms = { - loaders: ModuleOptions['rules']; + loaders: RuleSetRule[]; plugins: WebpackPluginInstance[]; }; diff --git a/packages/cli/src/lib/new/factories/backendPlugin.ts b/packages/cli/src/lib/new/factories/backendPlugin.ts index 9129a66075..050d05f0df 100644 --- a/packages/cli/src/lib/new/factories/backendPlugin.ts +++ b/packages/cli/src/lib/new/factories/backendPlugin.ts @@ -79,12 +79,12 @@ export const backendPlugin = createFactory({ }, ); }); - - await addToBackend(name, { - type: 'plugin', - }); } + await addToBackend(name, { + type: 'plugin', + }); + if (options.owner) { await addCodeownersEntry(`/plugins/${id}`, options.owner); } diff --git a/packages/cli/src/lib/new/factories/scaffolderModule.test.ts b/packages/cli/src/lib/new/factories/scaffolderModule.test.ts index c4b9d78fc1..60dade618a 100644 --- a/packages/cli/src/lib/new/factories/scaffolderModule.test.ts +++ b/packages/cli/src/lib/new/factories/scaffolderModule.test.ts @@ -26,6 +26,14 @@ import { import { scaffolderModule } from './scaffolderModule'; import { createMockDirectory } from '@backstage/backend-test-utils'; +const backendIndexTsContent = ` +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.start(); +`; + describe('scaffolderModule factory', () => { const mockDir = createMockDirectory(); @@ -41,6 +49,14 @@ describe('scaffolderModule factory', () => { it('should create a scaffolder backend module package', async () => { mockDir.setContent({ + packages: { + backend: { + 'package.json': JSON.stringify({}), + src: { + 'index.ts': backendIndexTsContent, + }, + }, + }, plugins: {}, }); @@ -84,8 +100,29 @@ describe('scaffolderModule factory', () => { 'copying module.ts', 'Installing:', `moving plugins${sep}scaffolder-backend-module-test`, + 'backend adding dependency', + 'backend adding module', ]); + await expect( + fs.readFile(mockDir.resolve('packages/backend/src/index.ts'), 'utf8'), + ).resolves.toBe(` +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('backstage-plugin-scaffolder-backend-module-test')); +backend.start(); +`); + + await expect( + fs.readJson(mockDir.resolve('packages/backend/package.json')), + ).resolves.toEqual({ + dependencies: { + 'backstage-plugin-scaffolder-backend-module-test': '^1.0.0', + }, + }); + await expect( fs.readJson( mockDir.resolve('plugins/scaffolder-backend-module-test/package.json'), diff --git a/packages/cli/src/lib/new/factories/scaffolderModule.ts b/packages/cli/src/lib/new/factories/scaffolderModule.ts index 4c279a49d4..6cea7a36ff 100644 --- a/packages/cli/src/lib/new/factories/scaffolderModule.ts +++ b/packages/cli/src/lib/new/factories/scaffolderModule.ts @@ -14,11 +14,12 @@ * limitations under the License. */ +import fs from 'fs-extra'; import chalk from 'chalk'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; import { CreateContext, createFactory } from '../types'; -import { Task } from '../../tasks'; +import { addPackageDependency, addToBackend, Task } from '../../tasks'; import { ownerPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; import { resolvePackageName } from './common/util'; @@ -82,6 +83,23 @@ export const scaffolderModule = createFactory({ }, }); + if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { + await Task.forItem('backend', 'adding dependency', async () => { + await addPackageDependency( + paths.resolveTargetRoot('packages/backend/package.json'), + { + dependencies: { + [name]: `^${ctx.defaultVersion}`, + }, + }, + ); + }); + } + + await addToBackend(name, { + type: 'module', + }); + if (options.owner) { await addCodeownersEntry(`/plugins/${slug}`, options.owner); } diff --git a/packages/cli/src/lib/packager/createDistWorkspace.ts b/packages/cli/src/lib/packager/createDistWorkspace.ts index b966e23a85..54ce9c073f 100644 --- a/packages/cli/src/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/lib/packager/createDistWorkspace.ts @@ -211,6 +211,7 @@ export async function createDistWorkspace( outputs: outputs, logPrefix: `${chalk.cyan(relativePath(paths.targetRoot, pkg.dir))}: `, minify: options.minify, + workspacePackages: packages, }); } } diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index 1222b2b4b8..058b37eb01 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -30,6 +30,11 @@ declare module '@svgr/rollup' { export default function svgr(options?: any): any; } +declare module 'rollup/parseAst' { + export function parseAst(code: string): any; + export function parseAstAsync(code: string): any; +} + declare module '@rollup/plugin-yaml'; declare module 'react-dev-utils/formatWebpackMessages' { export default function (stats: any): { diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 2a47e34b54..c93a71fe38 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/codemods +## 0.1.50-next.0 + +### Patch Changes + +- 0894166: Updated dependency `jscodeshift` to `^0.16.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + ## 0.1.49 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index d8c759c34e..38d32dfc74 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/codemods", - "version": "0.1.49", + "version": "0.1.50-next.0", "description": "A collection of codemods for Backstage projects", "backstage": { "role": "cli" diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index a60e86a6c8..aa1c4d7917 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/config-loader +## 1.9.1-next.0 + +### Patch Changes + +- ef3c507: Updated dependency `typescript-json-schema` to `^0.65.0`. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 1.9.0 ### Minor Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 98d233b348..e555f25119 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.9.0", + "version": "1.9.1-next.0", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" @@ -50,7 +50,7 @@ "lodash": "^4.17.21", "minimist": "^1.2.5", "node-fetch": "^2.7.0", - "typescript-json-schema": "^0.63.0", + "typescript-json-schema": "^0.65.0", "yaml": "^2.0.0" }, "devDependencies": { diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 2ae26f8b97..10b2cb59eb 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/core-app-api +## 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 diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 8b8cc991c8..3d2c44a148 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -196,6 +196,8 @@ export type AppIcons = { techdocs: IconComponent; user: IconComponent; warning: IconComponent; + star: IconComponent; + unstarred: IconComponent; }; // @public diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index ad8b4b49c0..e4bbdb3ed5 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "1.14.2", + "version": "1.14.3-next.0", "publishConfig": { "access": "public" }, @@ -66,7 +66,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/react-hooks": "^8.0.0", "@testing-library/user-event": "^14.0.0", "@types/zen-observable": "^0.8.0", diff --git a/packages/core-app-api/src/app/defaultConfigLoader.test.ts b/packages/core-app-api/src/app/defaultConfigLoader.test.tsx similarity index 67% rename from packages/core-app-api/src/app/defaultConfigLoader.test.ts rename to packages/core-app-api/src/app/defaultConfigLoader.test.tsx index c70ec3fbff..44d93aa625 100644 --- a/packages/core-app-api/src/app/defaultConfigLoader.test.ts +++ b/packages/core-app-api/src/app/defaultConfigLoader.test.tsx @@ -14,7 +14,10 @@ * limitations under the License. */ +import React from 'react'; +import { render } from '@testing-library/react'; import { defaultConfigLoaderSync } from './defaultConfigLoader'; +import { ConfigReader } from '@backstage/config'; (process as any).env = { NODE_ENV: 'test' }; const anyEnv = process.env as any; @@ -53,6 +56,45 @@ describe('defaultConfigLoaderSync', () => { ]); }); + it('loads config from script tags and ignore static config', () => { + anyEnv.APP_CONFIG = []; + + render( + , + ); + + const configs = (defaultConfigLoaderSync as any)('{"my":"runtime-config"}'); + expect(configs).toEqual([ + { data: { my: 'config' }, context: 'a' }, + { data: { my: 'override-config' }, context: 'b' }, + ]); + expect(ConfigReader.fromConfigs(configs).get('my')).toBe('override-config'); + }); + + it('loads config from all script tags in order', () => { + anyEnv.APP_CONFIG = []; + + render( + <> + + + , + ); + + const configs = (defaultConfigLoaderSync as any)('{"my":"runtime-config"}'); + expect(configs).toEqual([ + { data: { my: 'config' }, context: 'a' }, + { data: { my: 'override-config' }, context: 'b' }, + ]); + expect(ConfigReader.fromConfigs(configs).get('my')).toBe('override-config'); + }); + it('fails to load invalid missing config', () => { expect(() => defaultConfigLoaderSync()).toThrow( 'No static configuration provided', diff --git a/packages/core-app-api/src/app/defaultConfigLoader.ts b/packages/core-app-api/src/app/defaultConfigLoader.ts index 301b64a865..bfcef390ec 100644 --- a/packages/core-app-api/src/app/defaultConfigLoader.ts +++ b/packages/core-app-api/src/app/defaultConfigLoader.ts @@ -49,9 +49,36 @@ export function defaultConfigLoaderSync( } const configs = appConfig.slice() as unknown as AppConfig[]; - // Avoiding this string also being replaced at runtime - if ( + // Check if we have any config script tags, otherwise fall back to injected config + const configScripts = document.querySelectorAll( + 'script[type="backstage.io/config"]', + ); + if (configScripts.length > 0) { + for (const el of configScripts) { + try { + const content = el.textContent; + if (!content) { + throw new Error('tag is empty'); + } + let data; + try { + data = JSON.parse(content); + } catch (error) { + throw new Error(`failed to parse config; ${error}`); + } + if (!Array.isArray(data)) { + throw new Error('data is not an array'); + } + configs.push(...data); + } catch (error) { + throw new Error( + `Failed to load config from script tag, ${error.message}`, + ); + } + } + } else if ( runtimeConfigJson !== + // Avoiding this string also being replaced at runtime '__app_injected_runtime_config__'.toLocaleUpperCase('en-US') ) { try { diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 9d2c77292c..513063e974 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -115,6 +115,8 @@ export type AppIcons = { techdocs: IconComponent; user: IconComponent; warning: IconComponent; + star: IconComponent; + unstarred: IconComponent; }; /** diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 4e4047863c..0844fec4ff 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/core-compat-api +## 0.3.0-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + +## 0.3.0-next.1 + +### Minor Changes + +- 6db849e: **BREAKING**: The `namespace` parameter for API's is now defaulted to the `pluginId` which was discovered. This means that if you're overriding API's by using ID's directly, they might have changed to include the plugin ID too. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + ## 0.2.9-next.0 ### Patch Changes diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index e61ee451d9..ddb3bc6bf4 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -9,12 +9,12 @@ import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExtensionOverrides } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; +import { FrontendModule } from '@backstage/frontend-plugin-api'; import { FrontendPlugin } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; @@ -29,7 +29,7 @@ export function compatWrapper(element: ReactNode): React_2.JSX.Element; // @public (undocumented) export function convertLegacyApp( rootElement: React_2.JSX.Element, -): (FrontendPlugin | ExtensionOverrides)[]; +): (FrontendPlugin | FrontendModule | ExtensionOverrides)[]; // @public (undocumented) export function convertLegacyPageExtension( @@ -46,7 +46,7 @@ export function convertLegacyPlugin( options: { extensions: ExtensionDefinition[]; }, -): BackstagePlugin_2; +): FrontendPlugin; // @public export function convertLegacyRouteRef( diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 7cb411855f..5e8cc7ab50 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-compat-api", - "version": "0.2.9-next.0", + "version": "0.3.0-next.2", "backstage": { "role": "web-library" }, @@ -48,7 +48,7 @@ "@backstage/test-utils": "workspace:^", "@oriflame/backstage-plugin-score-card": "^0.8.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0" + "@testing-library/react": "^16.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index 4c2acd35a6..0319628b14 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -73,7 +73,7 @@ describe('collectLegacyRoutes', () => { defaultConfig: {}, }, { - id: 'api:plugin.scoringdata.service', + id: 'api:score-card/plugin.scoringdata.service', attachTo: { id: 'root', input: 'apis' }, disabled: false, }, @@ -89,7 +89,7 @@ describe('collectLegacyRoutes', () => { defaultConfig: {}, }, { - id: 'api:plugin.stackstorm.service', + id: 'api:stackstorm/plugin.stackstorm.service', attachTo: { id: 'root', input: 'apis' }, disabled: false, }, @@ -111,7 +111,7 @@ describe('collectLegacyRoutes', () => { defaultConfig: {}, }, { - id: 'api:plugin.puppetdb.service', + id: 'api:puppetDb/plugin.puppetdb.service', attachTo: { id: 'root', input: 'apis' }, disabled: false, }, @@ -209,7 +209,7 @@ describe('collectLegacyRoutes', () => { disabled: false, }, { - id: 'api:plugin.catalog.service', + id: 'api:catalog/plugin.catalog.service', attachTo: { id: 'root', input: 'apis', @@ -218,7 +218,7 @@ describe('collectLegacyRoutes', () => { disabled: false, }, { - id: 'api:catalog-react.starred-entities', + id: 'api:catalog/catalog-react.starred-entities', attachTo: { id: 'root', input: 'apis', @@ -227,7 +227,7 @@ describe('collectLegacyRoutes', () => { disabled: false, }, { - id: 'api:plugin.catalog.entity-presentation', + id: 'api:catalog/plugin.catalog.entity-presentation', attachTo: { id: 'root', input: 'apis', @@ -241,7 +241,7 @@ describe('collectLegacyRoutes', () => { id: 'score-card', extensions: [ { - id: 'api:plugin.scoringdata.service', + id: 'api:score-card/plugin.scoringdata.service', attachTo: { id: 'root', input: 'apis' }, disabled: false, }, diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 197c245278..43e66ce859 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -269,7 +269,7 @@ export function collectLegacyRoutes( ...extensions, ...Array.from(plugin.getApis()).map(factory => ApiBlueprint.make({ - namespace: factory.api.id, + name: factory.api.id, params: { factory }, }), ), diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx index bee379a792..18846e0f21 100644 --- a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -74,9 +74,9 @@ describe('BackwardsCompatProvider', () => { ); expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` - "plugins: app + "plugins: test, app components: NotFoundErrorPage, BootErrorPage, Progress, Router, ErrorBoundaryFallback - icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning" + icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning, star, unstarred" `); }); @@ -120,7 +120,7 @@ describe('ForwardsCompatProvider', () => { expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` "components: progress=true, notFoundErrorPage=true, errorBoundaryFallback=true - icons: kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, user, warning" + icons: kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, user, warning, star, unstarred" `); }); diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index b76a6c2d71..224ab46221 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -65,7 +65,7 @@ describe('convertLegacyApp', () => { defaultConfig: {}, }, { - id: 'api:plugin.scoringdata.service', + id: 'api:score-card/plugin.scoringdata.service', attachTo: { id: 'root', input: 'apis' }, disabled: false, }, @@ -81,7 +81,7 @@ describe('convertLegacyApp', () => { defaultConfig: {}, }, { - id: 'api:plugin.stackstorm.service', + id: 'api:stackstorm/plugin.stackstorm.service', attachTo: { id: 'root', input: 'apis' }, disabled: false, }, @@ -103,7 +103,7 @@ describe('convertLegacyApp', () => { defaultConfig: {}, }, { - id: 'api:plugin.puppetdb.service', + id: 'api:puppetDb/plugin.puppetdb.service', attachTo: { id: 'root', input: 'apis' }, disabled: false, }, diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index 2729664941..3f35c78408 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -22,12 +22,13 @@ import React, { isValidElement, } from 'react'; import { - ExtensionOverrides, + FrontendModule, FrontendPlugin, coreExtensionData, createExtension, + ExtensionOverrides, createExtensionInput, - createExtensionOverrides, + createFrontendModule, } from '@backstage/frontend-plugin-api'; import { getComponentData } from '@backstage/core-plugin-api'; import { collectLegacyRoutes } from './collectLegacyRoutes'; @@ -61,7 +62,7 @@ function selectChildren( /** @public */ export function convertLegacyApp( rootElement: React.JSX.Element, -): (FrontendPlugin | ExtensionOverrides)[] { +): (FrontendPlugin | FrontendModule | ExtensionOverrides)[] { if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') { return collectLegacyRoutes(rootElement); } @@ -104,7 +105,6 @@ export function convertLegacyApp( const [routesEl] = routesEls; const CoreLayoutOverride = createExtension({ - namespace: 'app', name: 'layout', attachTo: { id: 'app', input: 'root' }, inputs: { @@ -127,7 +127,6 @@ export function convertLegacyApp( }, }); const CoreNavOverride = createExtension({ - namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, output: [], @@ -139,7 +138,8 @@ export function convertLegacyApp( return [ ...collectedRoutes, - createExtensionOverrides({ + createFrontendModule({ + pluginId: 'app', extensions: [CoreLayoutOverride, CoreNavOverride], }), ]; diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx index 3d5b001c61..01266a35b7 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -84,7 +84,7 @@ describe('convertLegacyPlugin', () => { }); expect(internalConverted.featureFlags).toEqual([{ name: 'test-flag' }]); expect(internalConverted.extensions.map(e => e.id)).toEqual([ - 'api:plugin.test.client', + 'api:test/plugin.test.client', 'page:test', ]); }); diff --git a/packages/core-compat-api/src/convertLegacyPlugin.ts b/packages/core-compat-api/src/convertLegacyPlugin.ts index bf5759f214..5fd5e585e6 100644 --- a/packages/core-compat-api/src/convertLegacyPlugin.ts +++ b/packages/core-compat-api/src/convertLegacyPlugin.ts @@ -18,7 +18,7 @@ import { BackstagePlugin as LegacyBackstagePlugin } from '@backstage/core-plugin import { ApiBlueprint, ExtensionDefinition, - BackstagePlugin as NewBackstagePlugin, + FrontendPlugin as NewBackstagePlugin, createFrontendPlugin, } from '@backstage/frontend-plugin-api'; import { convertLegacyRouteRefs } from './convertLegacyRouteRef'; @@ -29,7 +29,7 @@ export function convertLegacyPlugin( options: { extensions: ExtensionDefinition[] }, ): NewBackstagePlugin { const apiExtensions = Array.from(legacyPlugin.getApis()).map(factory => - ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }), + ApiBlueprint.make({ name: factory.api.id, params: { factory } }), ); return createFrontendPlugin({ id: legacyPlugin.getId(), diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index f50b830702..033fddf9de 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/core-components +## 0.14.11-next.1 + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 0944334: Removed default title set to Unknown page on `ContentHeaderTitle` component to support usage of component without title prop. +- f325258: Use getOptionalString for optional app.support.items[].links[].title +- af4c146: Support menu items should not be buttons in favour of links +- 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). +- dbbd93e: Internal update to match recent React types +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.14.11-next.0 + +### Patch Changes + +- 06b8206: Added `titleComponent` prop to `SignInPage` component to allow further customization of the title using `ReactNode` +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/version-bridge@1.0.8 + ## 0.14.10 ### Patch Changes diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 597b1a9abf..ebcc053648 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -19,6 +19,7 @@ import { default as CSS_2 } from 'csstype'; import { CSSProperties } from 'react'; import { ElementType } from 'react'; import { ErrorInfo } from 'react'; +import IconButton from '@material-ui/core/IconButton'; import { IconComponent } from '@backstage/core-plugin-api'; import { Icons } from '@material-table/core'; import { IdentityApi } from '@backstage/core-plugin-api'; @@ -401,6 +402,24 @@ export type ErrorPanelProps = { title?: string; }; +// @public +export function FavoriteToggle( + props: ComponentProps & { + id: string; + title: string; + isFavorite: boolean; + onToggle: (value: boolean) => void; + }, +): React_2.JSX.Element; + +// @public +export function FavoriteToggleIcon(props: { + isFavorite: boolean; +}): React_2.JSX.Element; + +// @public (undocumented) +export type FavoriteToggleIconClassKey = 'icon' | 'iconBorder'; + // @public (undocumented) export type FeatureCalloutCircleClassKey = | '@keyframes pulsateSlightly' @@ -1186,6 +1205,9 @@ export function SimpleStepperStep( // @public (undocumented) export type SimpleStepperStepClassKey = 'end'; +// @public (undocumented) +export function StarIcon(props: IconComponentProps): React_2.JSX.Element; + // Warning: (ae-missing-release-tag) "StatusAborted" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1455,6 +1477,9 @@ export function TrendLine( }, ): React_2.JSX.Element | null; +// @public (undocumented) +export function UnstarredIcon(props: IconComponentProps): React_2.JSX.Element; + // @public export function useContent(): { focusContent: () => void; diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 154331d70d..1ac373b462 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.14.10", + "version": "0.14.11-next.1", "publishConfig": { "access": "public" }, @@ -99,7 +99,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/ansi-regex": "^5.0.0", "@types/classnames": "^2.2.9", diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx index a266c8c6cb..c4ed5ae072 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.stories.tsx @@ -52,7 +52,7 @@ greet(world) export const Default = () => ( - + ); diff --git a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.stories.tsx b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.stories.tsx new file mode 100644 index 0000000000..cfe44b8728 --- /dev/null +++ b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.stories.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { FavoriteToggle } from './FavoriteToggle'; +import { + UnifiedThemeProvider, + createBaseThemeOptions, + createUnifiedTheme, + palettes, +} from '@backstage/theme'; + +export default { + title: 'Core/FavoriteToggle', + component: FavoriteToggle, +}; + +export const Default = () => { + const [isFavorite, setFavorite] = React.useState(false); + return ( + + ); +}; + +const theme = createUnifiedTheme({ + ...createBaseThemeOptions({ + palette: palettes.dark, + }), + components: { + BackstageFavoriteToggleIcon: { + styleOverrides: { + icon: () => ({ color: 'aqua' }), + iconBorder: () => ({ color: 'white' }), + }, + }, + }, +}); + +export const WithThemeOverride = () => { + const [isFavorite, setFavorite] = React.useState(false); + return ( + + + + ); +}; diff --git a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.test.tsx b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.test.tsx new file mode 100644 index 0000000000..66b420b467 --- /dev/null +++ b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.test.tsx @@ -0,0 +1,57 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { FavoriteToggle } from './FavoriteToggle'; +import React from 'react'; +import userEvent from '@testing-library/user-event'; + +describe('', () => { + const onToggle = jest.fn(); + + const props = { + title: 'Favorite this thing', + id: 'some-thing-favorite', + onToggle, + isFavorite: true, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders with valid props', async () => { + const { getByRole } = await renderInTestApp(); + + expect(getByRole('button', { name: props.title })).toBeInTheDocument(); + }); + + it('should return inverted value on toggle', async () => { + const { getByRole } = await renderInTestApp(); + + await userEvent.click(getByRole('button', { name: props.title })); + expect(onToggle).toHaveBeenCalledWith(!props.isFavorite); + }); + + it('should show accessible tooltip', async () => { + const { findByRole, getByRole } = await renderInTestApp( + , + ); + + await userEvent.hover(getByRole('button', { name: props.title })); + + expect(await findByRole('tooltip')).toHaveTextContent(props.title); + }); +}); diff --git a/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.tsx b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.tsx new file mode 100644 index 0000000000..8a517d27ea --- /dev/null +++ b/packages/core-components/src/components/FavoriteToggle/FavoriteToggle.tsx @@ -0,0 +1,98 @@ +/* + * 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 React, { ComponentProps } from 'react'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; +import { Theme, makeStyles } from '@material-ui/core/styles'; +import { StarIcon, UnstarredIcon } from '../../icons'; + +const useStyles = makeStyles( + theme => ({ + icon: { + color: '#f3ba37', + cursor: 'pointer', + }, + iconBorder: { + color: theme.palette.text.primary, + cursor: 'pointer', + }, + }), + { name: 'BackstageFavoriteToggleIcon' }, +); + +/** + * @public + */ +export type FavoriteToggleIconClassKey = 'icon' | 'iconBorder'; + +/** + * Icon used in FavoriteToggle component. + * + * Can be used independently, useful when used as {@link @material-table/core#MaterialTableProps.actions} in {@link @material-table/core#MaterialTable} + * + * @public + */ +export function FavoriteToggleIcon(props: { isFavorite: boolean }) { + const { isFavorite } = props; + const classes = useStyles(); + + return ( + + {isFavorite ? : } + + ); +} + +/** + * Toggle encapsulating logic for marking something as favorite, + * primarily used in various instances of entity lists and cards but can be used elsewhere. + * + * This component can only be used in as a controlled toggle and does not keep internal state. + * + * @public + */ +export function FavoriteToggle( + props: ComponentProps & { + id: string; + title: string; + isFavorite: boolean; + onToggle: (value: boolean) => void; + }, +) { + const { + id, + title, + isFavorite: value, + onToggle: onChange, + ...iconButtonProps + } = props; + return ( + + onChange(!value)} + {...iconButtonProps} + > + + + + ); +} diff --git a/packages/core-components/src/components/FavoriteToggle/index.ts b/packages/core-components/src/components/FavoriteToggle/index.ts new file mode 100644 index 0000000000..460b162bdf --- /dev/null +++ b/packages/core-components/src/components/FavoriteToggle/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { FavoriteToggle, FavoriteToggleIcon } from './FavoriteToggle'; +export type { FavoriteToggleIconClassKey } from './FavoriteToggle'; diff --git a/packages/core-components/src/components/SupportButton/SupportButton.tsx b/packages/core-components/src/components/SupportButton/SupportButton.tsx index 8626353dd1..9e51e23119 100644 --- a/packages/core-components/src/components/SupportButton/SupportButton.tsx +++ b/packages/core-components/src/components/SupportButton/SupportButton.tsx @@ -67,7 +67,7 @@ const SupportLink = ({ link }: { link: SupportItemLink }) => ( const SupportListItem = ({ item }: { item: SupportItem }) => { return ( - + @@ -161,6 +161,7 @@ export function SupportButton(props: SupportButtonProps) { )} {React.Children.map(children, (child, i) => ( ), - Check: forwardRef((props, ref) => ), - Clear: forwardRef((props, ref) => ), - Delete: forwardRef((props, ref) => ), - DetailPanel: forwardRef((props, ref) => ( + Add: forwardRef((props, ref) => ( + + )), + Check: forwardRef((props, ref) => ( + + )), + Clear: forwardRef((props, ref) => ( + + )), + Delete: forwardRef((props, ref) => ( + + )), + DetailPanel: forwardRef((props, ref) => ( )), - Edit: forwardRef((props, ref) => ), - Export: forwardRef((props, ref) => ), - Filter: forwardRef((props, ref) => ), - FirstPage: forwardRef((props, ref) => ), - LastPage: forwardRef((props, ref) => ), - NextPage: forwardRef((props, ref) => ), - PreviousPage: forwardRef((props, ref) => ( + Edit: forwardRef((props, ref) => ( + + )), + Export: forwardRef((props, ref) => ( + + )), + Filter: forwardRef((props, ref) => ( + + )), + FirstPage: forwardRef((props, ref) => ( + + )), + LastPage: forwardRef((props, ref) => ( + + )), + NextPage: forwardRef((props, ref) => ( + + )), + PreviousPage: forwardRef((props, ref) => ( )), - ResetSearch: forwardRef((props, ref) => ), - Search: forwardRef((props, ref) => ), - SortArrow: forwardRef((props, ref) => ), - ThirdStateCheck: forwardRef((props, ref) => ), - ViewColumn: forwardRef((props, ref) => ), + ResetSearch: forwardRef((props, ref) => ( + + )), + Search: forwardRef((props, ref) => ( + + )), + SortArrow: forwardRef((props, ref) => ( + + )), + ThirdStateCheck: forwardRef((props, ref) => ( + + )), + ViewColumn: forwardRef((props, ref) => ( + + )), }; // TODO: Material table might already have such a function internally that we can use? diff --git a/packages/core-components/src/components/index.ts b/packages/core-components/src/components/index.ts index a58208fabb..828df3ec03 100644 --- a/packages/core-components/src/components/index.ts +++ b/packages/core-components/src/components/index.ts @@ -25,6 +25,7 @@ export * from './DependencyGraph'; export * from './DismissableBanner'; export * from './EmptyState'; export * from './ErrorPanel'; +export * from './FavoriteToggle'; export * from './ResponseErrorPanel'; export * from './FeatureDiscovery'; export * from './HeaderIconLinkRow'; @@ -47,3 +48,4 @@ export * from './TabbedLayout'; export * from './Table'; export * from './TrendLine'; export * from './WarningPanel'; +export * from './FavoriteToggle'; diff --git a/packages/core-components/src/hooks/useSupportConfig.ts b/packages/core-components/src/hooks/useSupportConfig.ts index 2b969d8b4d..d388728956 100644 --- a/packages/core-components/src/hooks/useSupportConfig.ts +++ b/packages/core-components/src/hooks/useSupportConfig.ts @@ -72,7 +72,7 @@ export function useSupportConfig(): SupportConfig { links: (itemConf.getOptionalConfigArray('links') ?? []).flatMap( linkConf => ({ url: linkConf.getString('url'), - title: linkConf.getString('title'), + title: linkConf.getOptionalString('title') ?? '', }), ), })), diff --git a/packages/core-components/src/icons/icons.tsx b/packages/core-components/src/icons/icons.tsx index ca3b6fa76a..bd1bf9e86e 100644 --- a/packages/core-components/src/icons/icons.tsx +++ b/packages/core-components/src/icons/icons.tsx @@ -95,3 +95,11 @@ export function UserIcon(props: IconComponentProps) { export function WarningIcon(props: IconComponentProps) { return ; } +/** @public */ +export function StarIcon(props: IconComponentProps) { + return ; +} +/** @public */ +export function UnstarredIcon(props: IconComponentProps) { + return ; +} diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.test.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.test.tsx index 37059766a8..5ae2efb806 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.test.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.test.tsx @@ -30,6 +30,13 @@ describe('', () => { rendered.getByText('Title'); }); + it('should render without title', async () => { + const rendered = await renderInTestApp( + content, + ); + rendered.getByText('content'); + }); + it('should render with titleComponent', async () => { const title = 'Custom title'; const titleComponent =

{title}

; diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx index 381919a170..e2a6e70620 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.tsx @@ -73,10 +73,7 @@ type ContentHeaderTitleProps = { className?: string; }; -const ContentHeaderTitle = ({ - title = 'Unknown page', - className, -}: ContentHeaderTitleProps) => ( +const ContentHeaderTitle = ({ title, className }: ContentHeaderTitleProps) => ( { const configApi = useApi(configApiRef); @@ -74,7 +76,13 @@ export const MultiSignInPage = ({
- {title && } + {(title || titleComponent) && ( + + )} >; registerApi< Api, @@ -37,6 +39,8 @@ export class DevAppBuilder { >(factory: ApiFactory): DevAppBuilder; registerPlugin(...plugins: BackstagePlugin[]): DevAppBuilder; render(): void; + setAvailableLanguages(languages: string[]): this; + setDefaultLanguage(language: string): this; } // @public (undocumented) @@ -55,6 +59,9 @@ export const EntityGridItem: ( }, ) => JSX.Element; +// @public (undocumented) +export const SidebarLanguageSwitcher: () => React_2.JSX.Element | null; + // @public export const SidebarSignOutButton: (props: { icon?: IconComponent; diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 45b32babf4..c42b180611 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.38-next.0", + "version": "1.1.0-next.2", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" @@ -54,7 +54,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "zen-observable": "^0.10.0" }, diff --git a/packages/dev-utils/src/components/SidebarLanguageSwitcher/SidebarLanguageSwitcher.test.tsx b/packages/dev-utils/src/components/SidebarLanguageSwitcher/SidebarLanguageSwitcher.test.tsx new file mode 100644 index 0000000000..fc620b846b --- /dev/null +++ b/packages/dev-utils/src/components/SidebarLanguageSwitcher/SidebarLanguageSwitcher.test.tsx @@ -0,0 +1,85 @@ +/* + * 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import ObservableImpl from 'zen-observable'; +import { + AppLanguageApi, + appLanguageApiRef, +} from '@backstage/core-plugin-api/alpha'; +import { SidebarLanguageSwitcher } from './SidebarLanguageSwitcher'; + +describe('SidebarLanguageSwitcher', () => { + let languageApi: jest.Mocked; + + beforeEach(() => { + languageApi = { + getAvailableLanguages: jest.fn(), + getLanguage: jest.fn(), + language$: jest.fn(), + setLanguage: jest.fn(), + }; + + languageApi.language$.mockReturnValue( + ObservableImpl.of<{ language?: string }>({ language: 'en' }), + ); + languageApi.getLanguage.mockReturnValue({ language: 'en' }); + languageApi.getAvailableLanguages.mockReturnValue({ + languages: ['en', 'fi'], + }); + }); + + it('should display current language', async () => { + const { getByLabelText, getByRole, getByText } = await renderInTestApp( + + + , + ); + + const button = getByLabelText('Language'); + expect(button).toBeInTheDocument(); + + await userEvent.click(button); + + expect(getByRole('listbox')).toBeInTheDocument(); + expect(getByText('English')).toBeInTheDocument(); + expect(getByText('English').parentElement?.parentElement).toHaveAttribute( + 'aria-selected', + 'true', + ); + }); + + it('should select different language', async () => { + const { getByLabelText, getByRole, getByText } = await renderInTestApp( + + + , + ); + + const button = getByLabelText('Language'); + expect(button).toBeInTheDocument(); + + await userEvent.click(button); + + expect(getByRole('listbox')).toBeInTheDocument(); + + await userEvent.click(getByText('suomi')); + + expect(languageApi.setLanguage).toHaveBeenCalledWith('fi'); + }); +}); diff --git a/packages/dev-utils/src/components/SidebarLanguageSwitcher/SidebarLanguageSwitcher.tsx b/packages/dev-utils/src/components/SidebarLanguageSwitcher/SidebarLanguageSwitcher.tsx new file mode 100644 index 0000000000..71fd9a6c94 --- /dev/null +++ b/packages/dev-utils/src/components/SidebarLanguageSwitcher/SidebarLanguageSwitcher.tsx @@ -0,0 +1,109 @@ +/* + * 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 React, { useState } from 'react'; +import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha'; +import TranslateIcon from '@material-ui/icons/Translate'; +import ListItemText from '@material-ui/core/ListItemText'; +import { useApi } from '@backstage/core-plugin-api'; +import useObservable from 'react-use/esm/useObservable'; +import { SidebarItem } from '@backstage/core-components'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; + +/** @public */ +export const SidebarLanguageSwitcher = () => { + const languageApi = useApi(appLanguageApiRef); + + const [languageObservable] = useState(() => languageApi.language$()); + const { language: currentLanguage } = useObservable( + languageObservable, + languageApi.getLanguage(), + ); + const [anchorEl, setAnchorEl] = useState(); + + const { languages } = languageApi.getAvailableLanguages(); + + if (languages.length <= 1) { + return null; + } + + const open = Boolean(anchorEl); + + const handleClose = () => { + setAnchorEl(undefined); + }; + + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleSetLanguage = (newLanguage: string | undefined) => { + languageApi.setLanguage(newLanguage); + setAnchorEl(undefined); + }; + + const getLanguageDisplayName = (language: string) => { + try { + const names = new Intl.DisplayNames([language], { + type: 'language', + }); + return names.of(language) || language; + } catch (err) { + return language; + } + }; + + return ( + <> + + + Choose language + {languages.map(lang => { + const active = currentLanguage === lang; + return ( + handleSetLanguage(lang)} + > + {getLanguageDisplayName(lang)} + + ); + })} + + + ); +}; diff --git a/packages/dev-utils/src/components/SidebarLanguageSwitcher/index.ts b/packages/dev-utils/src/components/SidebarLanguageSwitcher/index.ts new file mode 100644 index 0000000000..6726e0bb9d --- /dev/null +++ b/packages/dev-utils/src/components/SidebarLanguageSwitcher/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { SidebarLanguageSwitcher } from './SidebarLanguageSwitcher'; diff --git a/packages/dev-utils/src/components/index.ts b/packages/dev-utils/src/components/index.ts index 8959b6e7b1..7a27a97e73 100644 --- a/packages/dev-utils/src/components/index.ts +++ b/packages/dev-utils/src/components/index.ts @@ -16,3 +16,4 @@ export * from './EntityGridItem'; export * from './SidebarSignOutButton'; +export * from './SidebarLanguageSwitcher'; diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 3b3c993a6b..a22c4ffb71 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -40,6 +40,7 @@ import { IconComponent, RouteRef, } from '@backstage/core-plugin-api'; +import { TranslationResource } from '@backstage/core-plugin-api/alpha'; import { ScmIntegrationsApi, scmIntegrationsApiRef, @@ -50,7 +51,7 @@ import React, { ComponentType, PropsWithChildren, ReactNode } from 'react'; import { createRoutesFromChildren, Route } from 'react-router-dom'; import { SidebarThemeSwitcher } from './SidebarThemeSwitcher'; import 'react-dom'; -import { SidebarSignOutButton } from '../components'; +import { SidebarLanguageSwitcher, SidebarSignOutButton } from '../components'; let ReactDOMPromise: Promise< typeof import('react-dom') | typeof import('react-dom/client') @@ -98,9 +99,12 @@ export class DevAppBuilder { private readonly routes = new Array(); private readonly sidebarItems = new Array(); private readonly signInProviders = new Array(); + private readonly translationResources = new Array(); private defaultPage?: string; private themes?: Array; + private languages?: string[]; + private defaultLanguage?: string; /** * Register one or more plugins to render in the dev app @@ -193,6 +197,30 @@ export class DevAppBuilder { return this; } + /** + * Set available languages to be shown in the dev app + */ + setAvailableLanguages(languages: string[]) { + this.languages = languages; + return this; + } + + /** + * Add translation resource to the dev app + */ + addTranslationResource(resource: TranslationResource) { + this.translationResources.push(resource); + return this; + } + + /** + * Set default language for the dev app + */ + setDefaultLanguage(language: string) { + this.defaultLanguage = language; + return this; + } + /** * Build a DevApp component using the resources registered so far */ @@ -237,6 +265,11 @@ export class DevAppBuilder { bind(plugin.externalRoutes, targets); } }, + __experimentalTranslations: { + defaultLanguage: this.defaultLanguage, + availableLanguages: this.languages, + resources: this.translationResources, + }, }); const DevApp = ( @@ -252,6 +285,7 @@ export class DevAppBuilder { + diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index bec3eb9e36..5a16e02774 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,23 @@ # e2e-test +## 0.2.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.19-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + ## 0.2.20-next.0 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index d9dae4270c..9e32f2f93f 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.20-next.0", + "version": "0.2.20-next.2", "private": true, "backstage": { "role": "cli" diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 63a7650400..7860422920 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/eslint-plugin +## 0.1.9-next.0 + +### Patch Changes + +- 08895e3: Added support for linting dependencies on workspace packages with the `backstage.inline` flag. + ## 0.1.8 ### Patch Changes diff --git a/packages/eslint-plugin/lib/getPackages.js b/packages/eslint-plugin/lib/getPackages.js index 8f4ccfacf3..3b46f41a54 100644 --- a/packages/eslint-plugin/lib/getPackages.js +++ b/packages/eslint-plugin/lib/getPackages.js @@ -21,7 +21,7 @@ const manypkg = require('@manypkg/get-packages'); /** * @typedef ExtendedPackage - * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array }}} packageJson + * @type {import('@manypkg/get-packages').Package & { packageJson: { exports?: Record, files?: Array, backstage?: { inline?: boolean } }}} packageJson */ /** diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 0d8db000e0..fc019419cc 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/eslint-plugin", - "version": "0.1.8", + "version": "0.1.9-next.0", "description": "Backstage ESLint plugin", "publishConfig": { "access": "public" diff --git a/packages/eslint-plugin/rules/no-undeclared-imports.js b/packages/eslint-plugin/rules/no-undeclared-imports.js index d2c92ccb8a..b770ded365 100644 --- a/packages/eslint-plugin/rules/no-undeclared-imports.js +++ b/packages/eslint-plugin/rules/no-undeclared-imports.js @@ -22,11 +22,11 @@ const visitImports = require('../lib/visitImports'); const minimatch = require('minimatch'); const { execFileSync } = require('child_process'); -const depFields = { +const depFields = /** @type {const} */ ({ dep: 'dependencies', dev: 'devDependencies', peer: 'peerDependencies', -}; +}); const devModulePatterns = [ new minimatch.Minimatch('!src/**'), @@ -154,6 +154,124 @@ function addVersionQuery(name, flag, packages) { return `${name}@${mostCommonRange}`; } +/** + * Add missing package imports + * @param {Array<{name: string, flag: string, node: import('estree').Node}>} toAdd + * @param {import('../lib/getPackages').PackageMap} packages + * @param {import('../lib/getPackages').ExtendedPackage} localPkg + */ +function addMissingImports(toAdd, packages, localPkg) { + /** @type Record> */ + const byFlag = {}; + + for (const { name, flag } of toAdd) { + byFlag[flag] = byFlag[flag] ?? new Set(); + byFlag[flag].add(name); + } + + for (const name of byFlag[''] ?? []) { + byFlag['--dev']?.delete(name); + } + for (const name of byFlag['--peer'] ?? []) { + byFlag['']?.delete(name); + byFlag['--dev']?.delete(name); + } + + for (const [flag, names] of Object.entries(byFlag)) { + // Look up existing version queries in the repo for the same dependency + const namesWithQuery = [...names].map(name => + addVersionQuery(name, flag, packages), + ); + + // The security implication of this is a bit interesting, as crafted add-import + // directives could be used to install malicious packages. However, the same is true + // for adding malicious packages to package.json, so there's no significant difference. + execFileSync('yarn', ['add', ...(flag ? [flag] : []), ...namesWithQuery], { + cwd: localPkg.dir, + stdio: 'inherit', + }); + } +} + +/** + * Removes dependency entries pointing to inlined workspace packages. + * @param {Array<{pkg: import('../lib/getPackages').ExtendedPackage, node: import('estree').Node}>} toInline + * @param {import('../lib/getPackages').ExtendedPackage} localPkg + */ +function removeInlineImports(toInline, localPkg) { + /** @type Set */ + const toRemove = new Set(); + + for (const { pkg } of toInline) { + const name = pkg.packageJson.name; + for (const depType of Object.values(depFields)) { + if (localPkg.packageJson[depType]?.[name]) { + toRemove.add(name); + } + } + } + if (toRemove.size > 0) { + execFileSync('yarn', ['remove', ...toRemove], { + cwd: localPkg.dir, + stdio: 'inherit', + }); + } +} + +/** + * Adds dependencies that are not properly forwarded from inline dependencies. + * @param {Array<{pkg: import('../lib/getPackages').ExtendedPackage, node: import('estree').Node}>} toInline + * @param {import('../lib/getPackages').ExtendedPackage} localPkg + */ +function addForwardedInlineImports(toInline, localPkg) { + const declaredProdDeps = new Set([ + ...Object.keys(localPkg.packageJson.dependencies ?? {}), + ...Object.keys(localPkg.packageJson.peerDependencies ?? {}), + localPkg.packageJson.name, // include self + ]); + + /** @type Map> */ + const byFlagByName = new Map(); + + for (const { pkg } of toInline) { + for (const depType of /** @type {const} */ ([ + 'dependencies', + 'peerDependencies', + ])) { + for (const [depName, depQuery] of Object.entries( + pkg.packageJson[depType] ?? {}, + )) { + if (!declaredProdDeps.has(depName)) { + const flag = getAddFlagForDepsField(depType); + const byName = byFlagByName.get(flag); + if (byName) { + const query = byName.get(depName); + if (query && query !== depQuery) { + throw new Error( + `Conflicting dependency queries for inlined package dep ${depName}, got ${query} and ${depQuery}`, + ); + } else { + byName.set(depName, depQuery); + } + } else { + byFlagByName.set(flag, new Map([[depName, depQuery]])); + } + } + } + } + } + + for (const [flag, byName] of byFlagByName) { + const namesWithQuery = [...byName.entries()].map( + ([name, query]) => `${name}@${query}`, + ); + execFileSync('yarn', ['add', ...(flag ? [flag] : []), ...namesWithQuery], { + cwd: localPkg.dir, + stdio: 'inherit', + }); + } +} + /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -165,6 +283,8 @@ module.exports = { switch: '{{ packageName }} is declared in {{ oldDepsField }}, but should be moved to {{ depsField }} in {{ packageJsonPath }}.', switchBack: 'Switch back to import declaration', + inlineDirect: `The dependency on the inline package {{ packageName }} must not be declared in package dependencies.`, + inlineMissing: `Each production dependency from the inline package {{ packageName }} must be re-declared by this package, the following dependencies are missing: {{ missingDeps }}`, }, docs: { description: @@ -189,57 +309,48 @@ module.exports = { /** @type Array<{name: string, flag: string, node: import('estree').Node}> */ const importsToAdd = []; + /** @type Array<{pkg: import('../lib/getPackages').ExtendedPackage, node: import('estree').Node}> */ + const importsToInline = []; + return { // All missing imports that we detect are collected as we traverse, and then we use // the program exit to execute all install directives that have been found. ['Program:exit']() { - /** @type Record> */ - const byFlag = {}; + if (importsToAdd.length > 0) { + addMissingImports(importsToAdd, packages, localPkg); - for (const { name, flag } of importsToAdd) { - byFlag[flag] = byFlag[flag] ?? new Set(); - byFlag[flag].add(name); + // This switches all import directives back to the original import. + for (const added of importsToAdd) { + context.report({ + node: added.node, + messageId: 'switchBack', + fix(fixer) { + return fixer.replaceText(added.node, `'${added.name}'`); + }, + }); + } + importsToAdd.length = 0; } - for (const name of byFlag[''] ?? []) { - byFlag['--dev']?.delete(name); - } - for (const name of byFlag['--peer'] ?? []) { - byFlag['']?.delete(name); - byFlag['--dev']?.delete(name); + if (importsToInline.length > 0) { + removeInlineImports(importsToInline, localPkg); + addForwardedInlineImports(importsToInline, localPkg); + + for (const inlined of importsToInline) { + context.report({ + node: inlined.node, + messageId: 'switchBack', + fix(fixer) { + return fixer.replaceText( + inlined.node, + `'${inlined.pkg.packageJson.name}'`, + ); + }, + }); + } + importsToInline.length = 0; } - for (const [flag, names] of Object.entries(byFlag)) { - // Look up existing version queries in the repo for the same dependency - const namesWithQuery = [...names].map(name => - addVersionQuery(name, flag, packages), - ); - - // The security implication of this is a bit interesting, as crafted add-import - // directives could be used to install malicious packages. However, the same is true - // for adding malicious packages to package.json, so there's no significant difference. - execFileSync( - 'yarn', - ['add', ...(flag ? [flag] : []), ...namesWithQuery], - { - cwd: localPkg.dir, - stdio: 'inherit', - }, - ); - } - - // This switches all import directives back to the original import. - for (const added of importsToAdd) { - context.report({ - node: added.node, - messageId: 'switchBack', - fix(fixer) { - return fixer.replaceText(added.node, `'${added.name}'`); - }, - }); - } - - importsToAdd.length = 0; packages.clearCache(); }, ...visitImports(context, (node, imp) => { @@ -255,22 +366,99 @@ module.exports = { // Any import directive that is found is collected for processing later if (imp.type === 'directive') { - const parts = imp.path.split(':'); - if (parts[1] !== 'add-import') { - return; - } - const [type, name] = parts.slice(2); - if (!name.match(/^(@[-\w\.~]+\/)?[-\w\.~]*$/i)) { - throw new Error( - `Invalid package name to add as dependency: '${name}'`, - ); + const [, directive, ...args] = imp.path.split(':'); + + if (directive === 'add-import') { + const [type, name] = args; + if (!name.match(/^(@[-\w\.~]+\/)?[-\w\.~]*$/i)) { + throw new Error( + `Invalid package name to add as dependency: '${name}'`, + ); + } + + importsToAdd.push({ + flag: getAddFlagForDepsField(type).trim(), + name, + node: imp.node, + }); + } + + if (directive === 'inline-imports') { + const [name] = args; + const pkg = packages.map.get(name); + if (!pkg) { + throw new Error(`Unexpectedly missing inline package: ${name}`); + } + + importsToInline.push({ + pkg: pkg, + node: imp.node, + }); + } + + return; + } + + // Importing an internal inlined package, whose imports are inlined too + if ( + imp.type === 'internal' && + imp.package.packageJson.backstage?.inline + ) { + for (const depType of Object.values(depFields)) { + if (localPkg.packageJson[depType]?.[imp.packageName]) { + context.report({ + node, + messageId: 'inlineDirect', + data: { + packageName: imp.packageName, + }, + fix: fixer => { + return fixer.replaceText( + imp.node, + `'directive:inline-imports:${imp.packageName}'`, + ); + }, + }); + return; + } + } + + const missingDeps = []; + const declaredProdDeps = new Set([ + ...Object.keys(localPkg.packageJson.dependencies ?? {}), + ...Object.keys(localPkg.packageJson.peerDependencies ?? {}), + localPkg.packageJson.name, // include self + ]); + for (const depType of /** @type {const} */ ([ + 'dependencies', + 'peerDependencies', + ])) { + for (const depName of Object.keys( + imp.package.packageJson[depType] ?? {}, + )) { + if (!declaredProdDeps.has(depName)) { + missingDeps.push(depName); + } + } + } + + if (missingDeps.length > 0) { + context.report({ + node, + messageId: 'inlineMissing', + data: { + packageName: imp.packageName, + missingDeps: missingDeps.join(', '), + }, + fix: fixer => { + return fixer.replaceText( + imp.node, + `'directive:inline-imports:${imp.packageName}'`, + ); + }, + }); } - importsToAdd.push({ - flag: getAddFlagForDepsField(type).trim(), - name, - node: imp.node, - }); return; } diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json index efef27047b..ba55598dc2 100644 --- a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/bar/package.json @@ -1,14 +1,15 @@ { "name": "@internal/bar", + "backstage": { + "role": "frontend-plugin" + }, "exports": { ".": "./src/index.ts", "./BarPage": "./src/components/Bar.tsx", "./package.json": "./package.json" }, - "backstage": { - "role": "frontend-plugin" - }, "dependencies": { + "inline-dep": "*", "react-router": "*" }, "devDependencies": { diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-invalid-direct/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-invalid-direct/package.json new file mode 100644 index 0000000000..352a02d66c --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-invalid-direct/package.json @@ -0,0 +1,12 @@ +{ + "name": "@internal/inline-dep-direct", + "files": [ + "dist", + "type-utils" + ], + "dependencies": { + "@internal/inline": "workspace:^", + "@internal/inline-dep-valid": "workspace:^", + "react": "*" + } +} diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-invalid-missing/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-invalid-missing/package.json new file mode 100644 index 0000000000..595ee4a86b --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-invalid-missing/package.json @@ -0,0 +1,7 @@ +{ + "name": "@internal/inline-dep-missing", + "files": [ + "dist", + "type-utils" + ] +} diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-valid/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-valid/package.json new file mode 100644 index 0000000000..cbb42d0080 --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline-dep-valid/package.json @@ -0,0 +1,10 @@ +{ + "name": "@internal/inline-dep-valid", + "files": [ + "dist", + "type-utils" + ], + "peerDependencies": { + "react": "*" + } +} diff --git a/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline/package.json b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline/package.json new file mode 100644 index 0000000000..fb9345f140 --- /dev/null +++ b/packages/eslint-plugin/src/__fixtures__/monorepo/packages/inline/package.json @@ -0,0 +1,16 @@ +{ + "name": "@internal/inline", + "backstage": { + "inline": true + }, + "files": [ + "dist", + "type-utils" + ], + "dependencies": { + "@internal/inline-dep-valid": "workspace:^" + }, + "peerDependencies": { + "react": "*" + } +} diff --git a/packages/eslint-plugin/src/no-undeclared-imports.test.ts b/packages/eslint-plugin/src/no-undeclared-imports.test.ts index 29e2c3a87c..ee56c34926 100644 --- a/packages/eslint-plugin/src/no-undeclared-imports.test.ts +++ b/packages/eslint-plugin/src/no-undeclared-imports.test.ts @@ -52,6 +52,12 @@ const ERR_SWITCHED = ( const ERR_SWITCH_BACK = () => ({ message: 'Switch back to import declaration', }); +const ERR_INLINE_DIRECT = (name: string) => ({ + message: `The dependency on the inline package ${name} must not be declared in package dependencies.`, +}); +const ERR_INLINE_MISSING = (name: string, missing: string) => ({ + message: `Each production dependency from the inline package ${name} must be re-declared by this package, the following dependencies are missing: ${missing}`, +}); // cwd must be restored const origDir = process.cwd(); @@ -102,6 +108,17 @@ ruleTester.run(RULE, rule, { code: `require('lod' + 'ash')`, filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), }, + { + code: `import '@internal/inline'`, + filename: joinPath(FIXTURE, 'packages/inline-dep-valid/src/index.ts'), + }, + { + code: `import '@internal/inline'`, + filename: joinPath( + FIXTURE, + 'packages/inline-dep-valid/src/index.test.ts', + ), + }, ], invalid: [ { @@ -264,6 +281,29 @@ ruleTester.run(RULE, rule, { ), ], }, + { + code: `import '@internal/inline'`, + output: `import 'directive:inline-imports:@internal/inline'`, + filename: joinPath( + FIXTURE, + 'packages/inline-dep-invalid-direct/src/index.ts', + ), + errors: [ERR_INLINE_DIRECT('@internal/inline')], + }, + { + code: `import '@internal/inline'`, + output: `import 'directive:inline-imports:@internal/inline'`, + filename: joinPath( + FIXTURE, + 'packages/inline-dep-invalid-missing/src/index.ts', + ), + errors: [ + ERR_INLINE_MISSING( + '@internal/inline', + '@internal/inline-dep-valid, react', + ), + ], + }, // Switching back to original import declarations { @@ -302,5 +342,14 @@ ruleTester.run(RULE, rule, { filename: joinPath(FIXTURE, 'packages/bar/src/index.ts'), errors: [ERR_SWITCH_BACK()], }, + { + code: `import 'directive:inline-imports:@internal/inline'`, + output: `import '@internal/inline'`, + filename: joinPath( + FIXTURE, + 'packages/inline-dep-invalid-direct/src/index.ts', + ), + errors: [ERR_SWITCH_BACK()], + }, ], }); diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 878cebea2a..2ddc563d0a 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/frontend-app-api +## 0.9.0-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-defaults@0.1.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.9.0-next.1 + +### Minor Changes + +- 7c80650: **BREAKING**: The `createSpecializedApp` function now creates a bare-bones app without any of the default app structure or APIs. To re-introduce this functionality if you need to use `createSpecializedApp` you can install the `app` plugin from `@backstage/plugin-app`. + + In addition, the `createApp` and `CreateAppFeatureLoader` exports are now deprecated as they are being moved to `@backstage/frontend-defaults`, which should be used instead. + +### Patch Changes + +- c816e2d: Added support for new `FrontendPlugin` and `FrontendModule` types. +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-defaults@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.9.0-next.0 ### Minor Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 52078a4ff7..5bee47b3bd 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.9.0-next.0", + "version": "0.9.0-next.2", "backstage": { "role": "web-library" }, @@ -48,7 +48,7 @@ "@backstage/plugin-app": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0" + "@testing-library/react": "^16.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/packages/frontend-app-api/src/extensions/Root.ts b/packages/frontend-app-api/src/extensions/Root.ts index 3b1c237e7a..66c4db3140 100644 --- a/packages/frontend-app-api/src/extensions/Root.ts +++ b/packages/frontend-app-api/src/extensions/Root.ts @@ -22,7 +22,6 @@ import { } from '@backstage/frontend-plugin-api'; export const Root = createExtension({ - namespace: 'root', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { app: createExtensionInput([coreExtensionData.reactElement], { diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index 4e1c37e9a0..7ffe64d6d7 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -89,7 +89,9 @@ function routeInfoFromExtensions(extensions: ExtensionDefinition[]) { 'root', resolveAppNodeSpecs({ features: [appPlugin, plugin], - builtinExtensions: [resolveExtensionDefinition(Root)], + builtinExtensions: [ + resolveExtensionDefinition(Root, { namespace: 'root' }), + ], parameters: readAppExtensionsConfig(new MockConfigApi({})), forbidden: new Set(['root']), }), diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index 06d412b30e..f24a9e17f3 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -667,7 +667,6 @@ describe('instantiateAppNodeTree', () => { describe('v2', () => { const simpleExtension = resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef, otherDataRef.optional()], @@ -684,6 +683,7 @@ describe('instantiateAppNodeTree', () => { ]; }, }), + { namespace: 'app' }, ); function mirrorInputs(ctx: { @@ -750,7 +750,6 @@ describe('instantiateAppNodeTree', () => { makeSpec( resolveExtensionDefinition( createExtension({ - namespace: 'root-node', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { test: createExtensionInput([testDataRef]), @@ -758,6 +757,7 @@ describe('instantiateAppNodeTree', () => { output: [inputMirrorDataRef], factory: mirrorInputs, }), + { namespace: 'root-node' }, ), ), makeSpec(simpleExtension, { @@ -790,7 +790,6 @@ describe('instantiateAppNodeTree', () => { ...makeSpec( resolveExtensionDefinition( createExtension({ - namespace: 'root-node', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { test: createExtensionInput([testDataRef]), @@ -798,6 +797,7 @@ describe('instantiateAppNodeTree', () => { output: [inputMirrorDataRef], factory: mirrorInputs, }), + { namespace: 'root-node' }, ), ), }, @@ -873,7 +873,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -897,6 +896,7 @@ describe('instantiateAppNodeTree', () => { output: [inputMirrorDataRef], factory: mirrorInputs, }), + { namespace: 'app' }, ), ), }); @@ -946,7 +946,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef], @@ -956,6 +955,7 @@ describe('instantiateAppNodeTree', () => { throw error; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -972,7 +972,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef, testDataRef], @@ -980,6 +979,7 @@ describe('instantiateAppNodeTree', () => { return [testDataRef('test'), testDataRef('test2')]; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -996,7 +996,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [testDataRef], @@ -1004,6 +1003,7 @@ describe('instantiateAppNodeTree', () => { return [] as any; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -1021,7 +1021,6 @@ describe('instantiateAppNodeTree', () => { resolveExtensionDefinition( // @ts-expect-error createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [], // Output not declared @@ -1029,6 +1028,7 @@ describe('instantiateAppNodeTree', () => { return [testDataRef('test')] as any; }, }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -1045,7 +1045,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1056,6 +1055,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), attachments: new Map(), @@ -1090,7 +1090,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'parent', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1099,6 +1098,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1129,12 +1129,12 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'parent', attachTo: { id: 'ignored', input: 'ignored' }, output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1162,7 +1162,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1173,6 +1172,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1197,7 +1197,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1209,6 +1208,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), @@ -1227,7 +1227,6 @@ describe('instantiateAppNodeTree', () => { node: makeNode( resolveExtensionDefinition( createExtension({ - namespace: 'app', name: 'test', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { @@ -1238,6 +1237,7 @@ describe('instantiateAppNodeTree', () => { output: [], factory: () => [], }), + { namespace: 'app' }, ), ), }), diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx index 27f1d52822..612befc597 100644 --- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx +++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx @@ -206,7 +206,9 @@ export function createSpecializedApp(options?: { 'root', resolveAppNodeSpecs({ features, - builtinExtensions: [resolveExtensionDefinition(Root)], + builtinExtensions: [ + resolveExtensionDefinition(Root, { namespace: 'root' }), + ], parameters: readAppExtensionsConfig(config), forbidden: new Set(['root']), }), diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md new file mode 100644 index 0000000000..e6e27b1340 --- /dev/null +++ b/packages/frontend-defaults/CHANGELOG.md @@ -0,0 +1,30 @@ +# @backstage/frontend-defaults + +## 0.1.0-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/plugin-app@0.1.0-next.2 + - @backstage/frontend-app-api@0.9.0-next.2 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.0-next.0 + +### Minor Changes + +- 7c80650: Initial release of this package, which provides a default app setup through the `createApp` function. This replaces the existing `createApp` method from `@backstage/frontend-app-api`. + +### Patch Changes + +- 7d19cd5: Added a new `CreateAppOptions` type for the `createApp` options. +- 7d19cd5: Added `createPublicSignInApp`, used to creating apps for the public entry point. +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 diff --git a/packages/frontend-defaults/api-report.md b/packages/frontend-defaults/api-report.md index f1ef153e43..b99232b784 100644 --- a/packages/frontend-defaults/api-report.md +++ b/packages/frontend-defaults/api-report.md @@ -7,17 +7,11 @@ import { ConfigApi } from '@backstage/frontend-plugin-api'; import { CreateAppRouteBinder } from '@backstage/frontend-app-api'; import { FrontendFeature } from '@backstage/frontend-app-api'; import { JSX as JSX_2 } from 'react'; +import { default as React_2 } from 'react'; import { ReactNode } from 'react'; // @public -export function createApp(options?: { - features?: (FrontendFeature | CreateAppFeatureLoader)[]; - configLoader?: () => Promise<{ - config: ConfigApi; - }>; - bindRoutes?(context: { bind: CreateAppRouteBinder }): void; - loadingComponent?: ReactNode; -}): { +export function createApp(options?: CreateAppOptions): { createRoot(): JSX_2.Element; }; @@ -28,4 +22,22 @@ export interface CreateAppFeatureLoader { features: FrontendFeature[]; }>; } + +// @public +export interface CreateAppOptions { + // (undocumented) + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; + // (undocumented) + configLoader?: () => Promise<{ + config: ConfigApi; + }>; + // (undocumented) + features?: (FrontendFeature | CreateAppFeatureLoader)[]; + loadingComponent?: ReactNode; +} + +// @public +export function createPublicSignInApp(options?: CreateAppOptions): { + createRoot(): React_2.JSX.Element; +}; ``` diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index c70bb42424..36201d3800 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.0.0", + "version": "0.1.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0" + "@testing-library/react": "^16.0.0" }, "files": [ "dist" @@ -43,6 +43,7 @@ "@backstage/frontend-app-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-app": "workspace:^", + "@react-hookz/web": "^24.0.0", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0" }, "peerDependencies": { diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx index 1954554816..2890700653 100644 --- a/packages/frontend-defaults/src/createApp.test.tsx +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -284,9 +284,9 @@ describe('createApp', () => { components [ - - - + + + ] diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index 688e01534a..79c9eb4a16 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -50,11 +50,11 @@ export interface CreateAppFeatureLoader { } /** - * Creates a new Backstage frontend app instance. See https://backstage.io/docs/frontend-system/building-apps/index + * Options for {@link createApp}. * * @public */ -export function createApp(options?: { +export interface CreateAppOptions { features?: (FrontendFeature | CreateAppFeatureLoader)[]; configLoader?: () => Promise<{ config: ConfigApi }>; bindRoutes?(context: { bind: CreateAppRouteBinder }): void; @@ -65,7 +65,14 @@ export function createApp(options?: { * If set to "null" then no loading fallback component is rendered. * */ loadingComponent?: ReactNode; -}): { +} + +/** + * Creates a new Backstage frontend app instance. See https://backstage.io/docs/frontend-system/building-apps/index + * + * @public + */ +export function createApp(options?: CreateAppOptions): { createRoot(): JSX.Element; } { let suspenseFallback = options?.loadingComponent; diff --git a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx new file mode 100644 index 0000000000..ac1adf9b0f --- /dev/null +++ b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx @@ -0,0 +1,119 @@ +/* + * 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 { + IdentityApi, + SignInPageBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; +import { render, screen, waitFor } from '@testing-library/react'; +import React, { useEffect } from 'react'; +import { createPublicSignInApp } from './createPublicSignInApp'; +import { MockConfigApi } from '@backstage/test-utils'; + +describe('createPublicSignInApp', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should render a sign-in page', async () => { + const app = createPublicSignInApp({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + 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({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + features: [ + createFrontendModule({ + pluginId: 'app', + extensions: [ + SignInPageBlueprint.make({ + params: { + loader: + async () => + ({ onSignInSuccess }) => { + useEffect(() => { + onSignInSuccess({ + getCredentials: async () => ({ token: 'mock-token' }), + } as IdentityApi); + }, [onSignInSuccess]); + return
; + }, + }, + }), + ], + }), + ], + }); + + const { baseElement } = render(app.createRoot()); + + await waitFor(() => { + expect(submitSpy).toHaveBeenCalled(); + }); + + expect(baseElement).toMatchInlineSnapshot(` + +
+
+ + + +
+
+ + `); + }); +}); diff --git a/packages/frontend-defaults/src/createPublicSignInApp.tsx b/packages/frontend-defaults/src/createPublicSignInApp.tsx new file mode 100644 index 0000000000..a8fd456c73 --- /dev/null +++ b/packages/frontend-defaults/src/createPublicSignInApp.tsx @@ -0,0 +1,108 @@ +/* + * 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 { + coreExtensionData, + createFrontendModule, + identityApiRef, + useApi, +} from '@backstage/frontend-plugin-api'; +import React from 'react'; +import { useAsync, useMountEffect } from '@react-hookz/web'; +import { CreateAppOptions, createApp } from './createApp'; +import appPlugin from '@backstage/plugin-app'; + +// This is a copy of the CookieAuthRedirect component from the auth-react +// plugin, to avoid a dependency on that package. Long-term we want this to be +// the only implementation and remove the one in auth-react once the old frontend system is gone. + +// TODO(Rugvip): Should this be part of the app plugin instead? since it owns the backend part of it. + +/** @internal */ +export function InternalCookieAuthRedirect() { + const identityApi = useApi(identityApiRef); + + const [state, actions] = useAsync(async () => { + const { token } = await identityApi.getCredentials(); + if (!token) { + throw new Error('Expected Backstage token in sign-in response'); + } + return token; + }); + + useMountEffect(actions.execute); + + if (state.status === 'error' && state.error) { + return <>An error occurred: {state.error.message}; + } + + if (state.status === 'success' && state.result) { + return ( +
form?.submit()} + action={window.location.href} + method="POST" + style={{ visibility: 'hidden' }} + > + + + +
+ ); + } + + return null; +} + +/** + * Creates an app that is suitable for the public sign-in page, for use in the `index-public-experimental.tsx` file. + * + * @remarks + * + * This app has an override for the `app/layout` extension, which means that + * most extension typically installed in an app will be ignored. However, you + * can still for example install API and root element extensions. + * + * A typical setup of this app will only install a custom sign-in page. + * + * @example + * ```ts + * const app = createPublicSignInApp({ + * features: [signInPageModule], + * }); + * ``` + * + * @public + */ +export function createPublicSignInApp(options?: CreateAppOptions) { + return createApp({ + ...options, + features: [ + ...(options?.features ?? []), + // This is a rather than app plugin override in order for it to take precedence over any supplied app plugin override + createFrontendModule({ + pluginId: 'app', + extensions: [ + appPlugin.getExtension('app/layout').override({ + factory: () => [ + coreExtensionData.reactElement(), + ], + }), + ], + }), + ], + }); +} diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts index 913cd3934b..7ee60a2116 100644 --- a/packages/frontend-defaults/src/index.ts +++ b/packages/frontend-defaults/src/index.ts @@ -20,4 +20,9 @@ * @packageDocumentation */ -export { createApp, type CreateAppFeatureLoader } from './createApp'; +export { + createApp, + type CreateAppOptions, + type CreateAppFeatureLoader, +} from './createApp'; +export { createPublicSignInApp } from './createPublicSignInApp'; diff --git a/packages/frontend-internal/.eslintrc.js b/packages/frontend-internal/.eslintrc.js new file mode 100644 index 0000000000..e487f765b2 --- /dev/null +++ b/packages/frontend-internal/.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/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md new file mode 100644 index 0000000000..cea0a76fd4 --- /dev/null +++ b/packages/frontend-internal/CHANGELOG.md @@ -0,0 +1,10 @@ +# @internal/frontend + +## 0.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/types@1.1.1 diff --git a/packages/frontend-internal/README.md b/packages/frontend-internal/README.md new file mode 100644 index 0000000000..72e54f21cf --- /dev/null +++ b/packages/frontend-internal/README.md @@ -0,0 +1,3 @@ +# @internal/frontend + +This is an internal package used by the other frontend packages. It does not get published to NPM, but instead inlined into consuming packages due to the `backstage.inline` flag in `package.json`. diff --git a/packages/frontend-internal/catalog-info.yaml b/packages/frontend-internal/catalog-info.yaml new file mode 100644 index 0000000000..630f53275a --- /dev/null +++ b/packages/frontend-internal/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: internal-frontend + title: '@internal/frontend' +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json new file mode 100644 index 0000000000..66c4c2351b --- /dev/null +++ b/packages/frontend-internal/package.json @@ -0,0 +1,39 @@ +{ + "name": "@internal/frontend", + "version": "0.0.1-next.0", + "backstage": { + "role": "web-library", + "inline": true + }, + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/frontend-internal" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "lint": "backstage-cli package lint", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/types": "workspace:^", + "@backstage/version-bridge": "workspace:^", + "zod": "^3.22.4" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/frontend-app-api": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^16.0.0" + } +} diff --git a/packages/frontend-internal/src/index.ts b/packages/frontend-internal/src/index.ts new file mode 100644 index 0000000000..a5728f2ff6 --- /dev/null +++ b/packages/frontend-internal/src/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 './wiring'; diff --git a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts new file mode 100644 index 0000000000..840e45ff4a --- /dev/null +++ b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts @@ -0,0 +1,104 @@ +/* + * 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 { + AnyExtensionDataRef, + ApiHolder, + AppNode, + ExtensionDataValue, + ExtensionDefinition, + ExtensionDefinitionParameters, + ExtensionInput, + PortableSchema, + ResolvedExtensionInputs, +} from '@backstage/frontend-plugin-api'; + +export type InternalExtensionDefinition< + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> = ExtensionDefinition & { + readonly kind?: string; + readonly namespace?: string; + readonly name?: string; + readonly attachTo: { id: string; input: string }; + readonly disabled: boolean; + readonly configSchema?: PortableSchema; +} & ( + | { + readonly version: 'v1'; + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: object; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; + } + | { + readonly version: 'v2'; + readonly inputs: { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }; + readonly output: Array; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: object; + inputs: ResolvedExtensionInputs<{ + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }>; + }): Iterable>; + } + ); + +/** @internal */ +export function toInternalExtensionDefinition< + T extends ExtensionDefinitionParameters, +>(overrides: ExtensionDefinition): InternalExtensionDefinition { + const internal = overrides as InternalExtensionDefinition; + if (internal.$$type !== '@backstage/ExtensionDefinition') { + throw new Error( + `Invalid extension definition instance, bad type '${internal.$$type}'`, + ); + } + const version = internal.version; + if (version !== 'v1' && version !== 'v2') { + throw new Error( + `Invalid extension definition instance, bad version '${version}'`, + ); + } + return internal; +} diff --git a/packages/frontend-internal/src/wiring/index.ts b/packages/frontend-internal/src/wiring/index.ts new file mode 100644 index 0000000000..d0aefbed64 --- /dev/null +++ b/packages/frontend-internal/src/wiring/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { + toInternalExtensionDefinition, + type InternalExtensionDefinition, +} from './InternalExtensionDefinition'; diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index ff8776070b..1882f5904f 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/frontend-plugin-api +## 0.8.0-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 043d7cd: Internal refactor +- 2a61422: The `factory` option is no longer required when overriding an extension. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/types@1.1.1 + +## 0.8.0-next.1 + +### Patch Changes + +- c816e2d: Added `createFrontendModule` as a replacement for `createExtensionOverrides`, which is now deprecated. + + Deprecated the `BackstagePlugin` and `FrontendFeature` type in favor of `FrontendPlugin` and `FrontendFeature` from `@backstage/frontend-app-api` respectively. + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.8.0-next.0 ### Minor Changes diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index b42efddffa..1b89aeac74 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -454,7 +454,7 @@ export function createComponentExtension(options: { >; }; kind: 'component'; - namespace: string; + namespace: undefined; name: string; }>; @@ -477,6 +477,55 @@ export function createComponentRef(options: { }): ComponentRef; // @public (undocumented) +export function createExtension< + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + TConfigSchema extends { + [key: string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, +>( + options: CreateExtensionOptions< + TKind, + undefined, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, +): ExtensionDefinition<{ + 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; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}>; + +// @public @deprecated (undocumented) export function createExtension< UOutput extends AnyExtensionDataRef, TInputs extends { @@ -526,6 +575,63 @@ export function createExtension< }>; // @public +export function createExtensionBlueprint< + TParams extends object, + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + TConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends undefined = undefined, + TName extends string | undefined = undefined, + TDataRefs extends { + [name in string]: AnyExtensionDataRef; + } = never, +>( + options: CreateExtensionBlueprintOptions< + TKind, + undefined, + TName, + TParams, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput, + TDataRefs + >, +): ExtensionBlueprint<{ + kind: TKind; + namespace: undefined; + name: TName; + params: TParams; + output: UOutput; + 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; +}>; + +// @public @deprecated (undocumented) export function createExtensionBlueprint< TParams extends object, UOutput extends AnyExtensionDataRef, @@ -857,6 +963,28 @@ export interface ExtensionBlueprint< // (undocumented) dataRefs: T['dataRefs']; // (undocumented) + make< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + >(args: { + namespace?: undefined; + name?: TNewName; + attachTo?: { + id: string; + input: string; + }; + disabled?: boolean; + params: T['params']; + }): ExtensionDefinition<{ + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + config: T['config']; + configInput: T['configInput']; + output: T['output']; + inputs: T['inputs']; + }>; + // @deprecated (undocumented) make< TNewNamespace extends string | undefined, TNewName extends string | undefined, @@ -898,7 +1026,94 @@ export interface ExtensionBlueprint< >; }, >(args: { - namespace?: TNewNamespace; + namespace?: undefined; + name?: TNewName; + attachTo?: { + id: string; + input: string; + }; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory( + originalFactory: ( + params: T['params'], + context?: { + config?: T['config']; + inputs?: ResolveInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): ExtensionDefinition<{ + 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] + >; + }> + >) & + T['configInput']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + }>; + // @deprecated (undocumented) + makeWithOverrides< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + >(args: { + namespace: TNewNamespace; name?: TNewName; attachTo?: { id: string; @@ -1108,7 +1323,7 @@ export type ExtensionDefinition< string}' is already defined in parent schema`; }; }; - factory( + factory?( originalFactory: (context?: { config?: T['config']; inputs?: ResolveInputValueOverrides>; @@ -1289,7 +1504,7 @@ export { googleAuthApiRef }; // @public (undocumented) export const IconBundleBlueprint: ExtensionBlueprint<{ kind: 'icon-bundle'; - namespace: 'app'; + namespace: undefined; name: undefined; params: { icons: { @@ -1701,7 +1916,7 @@ export interface SubRouteRef< // @public export const ThemeBlueprint: ExtensionBlueprint<{ kind: 'theme'; - namespace: 'app'; + namespace: undefined; name: undefined; params: { theme: AppTheme; diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 540a8b54fe..08ae922350 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.8.0-next.0", + "version": "0.8.0-next.2", "backstage": { "role": "web-library" }, @@ -47,7 +47,7 @@ "@backstage/frontend-test-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "history": "^5.3.0" }, "peerDependencies": { diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 800eb5d7b1..519b7a4d1a 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -31,7 +31,7 @@ describe('ApiBlueprint', () => { params: { factory, }, - namespace: 'test', + name: 'test', }); expect(extension).toMatchInlineSnapshot(` @@ -47,8 +47,8 @@ describe('ApiBlueprint', () => { "factory": [Function], "inputs": {}, "kind": "api", - "name": undefined, - "namespace": "test", + "name": "test", + "namespace": undefined, "output": [ [Function], ], @@ -72,7 +72,7 @@ describe('ApiBlueprint', () => { inputs: { test: createExtensionInput([ApiBlueprint.dataRefs.factory]), }, - namespace: api.id, + name: api.id, factory(originalFactory, { config: _config, inputs: _inputs }) { return originalFactory({ factory: createApiFactory({ @@ -122,8 +122,8 @@ describe('ApiBlueprint', () => { }, }, "kind": "api", - "name": undefined, - "namespace": "test", + "name": "test", + "namespace": undefined, "output": [ [Function], ], diff --git a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx index cda0085fec..f9214121ce 100644 --- a/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/AppRootWrapperBlueprint.test.tsx @@ -72,7 +72,6 @@ describe('AppRootWrapperBlueprint', () => { it('should render the complex component wrapper', async () => { const extension = AppRootWrapperBlueprint.makeWithOverrides({ - name: 'test', config: { schema: { name: z => z.string(), diff --git a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts index 369ac052f5..51b3b6d633 100644 --- a/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/IconBundleBlueprint.ts @@ -24,7 +24,6 @@ const iconsDataRef = createExtensionDataRef<{ /** @public */ export const IconBundleBlueprint = createExtensionBlueprint({ kind: 'icon-bundle', - namespace: 'app', attachTo: { id: 'api:app/icons', input: 'icons' }, output: [iconsDataRef], config: { diff --git a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx index 3062d6d9de..ab6bb1eb1f 100644 --- a/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/RouterBlueprint.test.tsx @@ -59,7 +59,6 @@ describe('RouterBlueprint', () => { it('should work with simple options', async () => { const extension = RouterBlueprint.make({ - namespace: 'test', params: { Component: ({ children }) => ( @@ -86,7 +85,6 @@ describe('RouterBlueprint', () => { it('should work with complex options and props', async () => { const extension = RouterBlueprint.makeWithOverrides({ - namespace: 'test', name: 'test', config: { schema: { @@ -115,9 +113,8 @@ describe('RouterBlueprint', () => { config: { name: 'Robin' }, }).add( createExtension({ - namespace: 'test', attachTo: { - id: 'app-router-component:test/test', + id: 'app-router-component:test', input: 'children', }, output: [coreExtensionData.reactElement], diff --git a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx index 2637873930..a85b5e6f39 100644 --- a/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/SignInPageBlueprint.test.tsx @@ -57,7 +57,6 @@ describe('SignInPageBlueprint', () => { const MockSignInPage = () =>
; const extension = SignInPageBlueprint.make({ - name: 'test', params: { loader: async () => () => }, }); diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts index 6d3f6280a3..89c2229fab 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.test.ts @@ -42,7 +42,7 @@ describe('ThemeBlueprint', () => { "inputs": {}, "kind": "theme", "name": "light", - "namespace": "app", + "namespace": undefined, "output": [ [Function], ], diff --git a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts index ca33970125..323078bc4e 100644 --- a/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/ThemeBlueprint.ts @@ -28,7 +28,6 @@ const themeDataRef = createExtensionDataRef().with({ */ export const ThemeBlueprint = createExtensionBlueprint({ kind: 'theme', - namespace: 'app', attachTo: { id: 'api:app/app-theme', input: 'themes' }, output: [themeDataRef], dataRefs: { diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index c8fa907bc8..f561a73dc6 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -33,8 +33,7 @@ export function createComponentExtension(options: { }) { return createExtension({ kind: 'component', - namespace: options.ref.id, - name: options.name, + name: options.name ?? options.ref.id, attachTo: { id: 'api:app/components', input: 'components' }, disabled: options.disabled, output: [createComponentExtension.componentDataRef], diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 68478a756e..b5d846871f 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -31,7 +31,6 @@ function unused(..._any: any[]) {} describe('createExtension', () => { it('should create an extension with a simple output', () => { const baseConfig = { - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], }; @@ -41,7 +40,7 @@ describe('createExtension', () => { return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); // Member arrow function declaration createExtension({ @@ -161,7 +160,6 @@ describe('createExtension', () => { it('should create an extension with a some optional output', () => { const baseConfig = { - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], }; @@ -169,7 +167,7 @@ describe('createExtension', () => { ...baseConfig, factory: () => [stringDataRef('bar')], }); - expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); createExtension({ ...baseConfig, @@ -206,7 +204,6 @@ describe('createExtension', () => { it('should create an extension with input', () => { const extension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, inputs: { mixed: createExtensionInput([stringDataRef, numberDataRef.optional()]), @@ -250,15 +247,14 @@ describe('createExtension', () => { return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); expect(String(extension)).toBe( - 'ExtensionDefinition{namespace=test,attachTo=root@default}', + 'ExtensionDefinition{attachTo=root@default}', ); }); it('should create an extension with config', () => { const extension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, config: { schema: { @@ -284,9 +280,9 @@ describe('createExtension', () => { return [stringDataRef('bar')]; }, }); - expect(extension).toMatchObject({ version: 'v2', namespace: 'test' }); + expect(extension).toMatchObject({ version: 'v2' }); expect(String(extension)).toBe( - 'ExtensionDefinition{namespace=test,attachTo=root@default}', + 'ExtensionDefinition{attachTo=root@default}', ); expect( @@ -330,7 +326,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], factory() { @@ -342,7 +337,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], factory() { @@ -354,7 +348,6 @@ describe('createExtension', () => { // Duplicate output, we won't attempt to handle this a compile time and instead error out at runtime expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], factory() { @@ -366,7 +359,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], factory() { @@ -377,7 +369,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], factory() { @@ -388,7 +379,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], factory() { @@ -399,7 +389,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], factory() { @@ -413,7 +402,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], *factory() { @@ -425,7 +413,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], *factory() { @@ -437,7 +424,6 @@ describe('createExtension', () => { // Duplicate output, we won't attempt to handle this a compile time and instead error out at runtime expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], *factory() { @@ -450,7 +436,6 @@ describe('createExtension', () => { expect( // @ts-expect-error createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], *factory() { @@ -462,7 +447,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], *factory() { @@ -474,7 +458,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], *factory() { @@ -486,7 +469,6 @@ describe('createExtension', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef.optional()], *factory() { @@ -499,7 +481,6 @@ describe('createExtension', () => { it('should support new form of inputs', () => { expect( createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'default' }, inputs: { header: createExtensionInput([stringDataRef.optional()], { @@ -534,7 +515,6 @@ describe('createExtension', () => { describe('overrides', () => { it('should allow overriding of config and merging', () => { const testExtension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'blob' }, output: [stringDataRef], config: { @@ -563,7 +543,6 @@ describe('createExtension', () => { it('should allow overriding of outputs', () => { const testExtension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'blob' }, output: [stringDataRef], inputs: { @@ -588,7 +567,7 @@ describe('createExtension', () => { }, }); - // @ts-expect-error - this should fail because string output should be merged? + // @ts-expect-error const override2 = testExtension.override({ output: [numberDataRef], factory(_, { inputs }) { @@ -603,7 +582,6 @@ describe('createExtension', () => { it('should allow overriding the factory function and calling the original factory', () => { const testExtension = createExtension({ - namespace: 'test', attachTo: { id: 'root', input: 'blob' }, output: [stringDataRef], config: { @@ -635,7 +613,6 @@ describe('createExtension', () => { it('should allow overriding the returned values from the parent factory', () => { const testExtension = createExtension({ kind: 'thing', - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef, numberDataRef], config: { @@ -666,7 +643,6 @@ describe('createExtension', () => { it('should work functionally with overrides', () => { const testExtension = createExtension({ kind: 'thing', - namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: [stringDataRef], config: { @@ -705,6 +681,69 @@ describe('createExtension', () => { ).toBe('foo-hello-override-world'); }); + it('should be able to disable extension with override', () => { + const subject = createExtension({ + name: 'root', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + input: createExtensionInput([stringDataRef], { + singleton: true, + optional: true, + }), + }, + output: [stringDataRef.optional()], + factory({ inputs }) { + return inputs.input ?? []; + }, + }); + + const attached = createExtension({ + attachTo: { id: 'root', input: 'input' }, + output: [stringDataRef], + factory() { + return [stringDataRef('test')]; + }, + }); + + expect( + createExtensionTester(subject).add(attached).get(stringDataRef), + ).toBe('test'); + + expect( + createExtensionTester(subject) + .add(attached.override({ disabled: true })) + .get(stringDataRef), + ).toBe(undefined); + }); + + it('should complain when overriding with incompatible output', () => { + const testExtension = createExtension({ + namespace: 'test', + attachTo: { id: 'root', input: 'blob' }, + output: [stringDataRef], + factory() { + return [stringDataRef('0')]; + }, + }); + + // @ts-expect-error - override output is incompatible with factory + const override = testExtension.override({ + output: [numberDataRef], + factory() { + return [stringDataRef('1')]; + }, + }); + expect(override).toBeDefined(); + + expect(() => + testExtension.override({ + output: [numberDataRef], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Refused to override output without also overriding factory"`, + ); + }); + it('should be able to override input values', () => { const outputRef = createExtensionDataRef().with({ id: 'output', diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 445bbd8a68..7aef3fc1b5 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -15,7 +15,6 @@ */ import { ApiHolder, AppNode } from '../apis'; -import { PortableSchema } from '../schema'; import { Expand } from '../types'; import { ResolveInputValueOverrides, @@ -32,6 +31,7 @@ import { import { ExtensionInput } from './createExtensionInput'; import { z } from 'zod'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; +import { InternalExtensionDefinition } from '@internal/frontend'; /** * Convert a single extension input into a matching resolved input. @@ -191,7 +191,7 @@ export type ExtensionDefinition< string}' is already defined in parent schema`; }; }; - factory( + factory?( originalFactory: (context?: { config?: T['config']; inputs?: ResolveInputValueOverrides>; @@ -235,85 +235,95 @@ export type ExtensionDefinition< }>; }; -/** @internal */ -export type InternalExtensionDefinition< - T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, -> = ExtensionDefinition & { - readonly kind?: string; - readonly namespace?: string; - readonly name?: string; - readonly attachTo: { id: string; input: string }; - readonly disabled: boolean; - readonly configSchema?: PortableSchema; -} & ( - | { - readonly version: 'v1'; - readonly inputs: { - [inputName in string]: { - $$type: '@backstage/ExtensionInput'; - extensionData: { - [name in string]: AnyExtensionDataRef; - }; - config: { optional: boolean; singleton: boolean }; - }; - }; - readonly output: { - [name in string]: AnyExtensionDataRef; - }; - factory(context: { - node: AppNode; - apis: ApiHolder; - config: object; - inputs: { - [inputName in string]: unknown; - }; - }): { - [inputName in string]: unknown; - }; - } - | { - readonly version: 'v2'; - readonly inputs: { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }; - readonly output: Array; - factory(context: { - node: AppNode; - apis: ApiHolder; - config: object; - inputs: ResolvedExtensionInputs<{ - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }>; - }): Iterable>; - } - ); - -/** @internal */ -export function toInternalExtensionDefinition< - T extends ExtensionDefinitionParameters, ->(overrides: ExtensionDefinition): InternalExtensionDefinition { - const internal = overrides as InternalExtensionDefinition; - if (internal.$$type !== '@backstage/ExtensionDefinition') { - throw new Error( - `Invalid extension definition instance, bad type '${internal.$$type}'`, - ); - } - const version = internal.version; - if (version !== 'v1' && version !== 'v2') { - throw new Error( - `Invalid extension definition instance, bad version '${version}'`, - ); - } - return internal; -} - /** @public */ +export function createExtension< + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, +>( + options: CreateExtensionOptions< + TKind, + undefined, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, +): ExtensionDefinition<{ + 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; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}>; +/** + * @public + * @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. + */ +export function createExtension< + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfigSchema extends { [key: string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, + const TKind extends string | undefined = undefined, + const TNamespace extends string | undefined = undefined, + const TName extends string | undefined = undefined, +>( + options: CreateExtensionOptions< + TKind, + TNamespace, + TName, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput + >, +): ExtensionDefinition<{ + 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; + }> + >; + output: UOutput; + inputs: TInputs; + kind: string | undefined extends TKind ? undefined : TKind; + namespace: string | undefined extends TNamespace ? undefined : TNamespace; + name: string | undefined extends TName ? undefined : TName; +}>; export function createExtension< UOutput extends AnyExtensionDataRef, TInputs extends { @@ -430,6 +440,15 @@ export function createExtension< UFactoryOutput >; + // 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. + if (overrideOptions.output && !overrideOptions.factory) { + throw new Error( + 'Refused to override output without also overriding factory', + ); + } + return createExtension({ kind: newOptions.kind, namespace: newOptions.namespace, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index fc77c63e97..47ef7a75b4 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -27,11 +27,9 @@ import { } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; -import { - ExtensionDefinition, - toInternalExtensionDefinition, -} from './createExtension'; +import { ExtensionDefinition } from './createExtension'; import { createExtensionDataContainer } from './createExtensionDataContainer'; +import { toInternalExtensionDefinition } from '@internal/frontend'; function unused(..._any: any[]) {} diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 77d078f257..40d1284b15 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -107,6 +107,25 @@ export interface ExtensionBlueprint< > { dataRefs: T['dataRefs']; + make< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + >(args: { + namespace?: undefined; + name?: TNewName; + attachTo?: { id: string; input: string }; + disabled?: boolean; + params: T['params']; + }): ExtensionDefinition<{ + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + config: T['config']; + configInput: T['configInput']; + output: T['output']; + inputs: T['inputs']; + }>; + /** @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. */ make< TNewNamespace extends string | undefined, TNewName extends string | undefined, @@ -149,7 +168,88 @@ export interface ExtensionBlueprint< >; }, >(args: { - namespace?: TNewNamespace; + namespace?: undefined; + name?: TNewName; + attachTo?: { id: string; input: string }; + disabled?: boolean; + inputs?: TExtraInputs & { + [KName in keyof T['inputs']]?: `Error: Input '${KName & + string}' is already defined in parent definition`; + }; + output?: Array; + config?: { + schema: TExtensionConfigSchema & { + [KName in keyof T['config']]?: `Error: Config key '${KName & + string}' is already defined in parent schema`; + }; + }; + factory( + originalFactory: ( + params: T['params'], + context?: { + config?: T['config']; + inputs?: ResolveInputValueOverrides>; + }, + ) => ExtensionDataContainer>, + context: { + node: AppNode; + apis: ApiHolder; + config: T['config'] & { + [key in keyof TExtensionConfigSchema]: z.infer< + ReturnType + >; + }; + inputs: Expand>; + }, + ): Iterable & + VerifyExtensionFactoryOutput< + AnyExtensionDataRef extends UNewOutput + ? NonNullable + : UNewOutput, + UFactoryOutput + >; + }): ExtensionDefinition<{ + 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] + >; + }> + >) & + T['configInput']; + output: AnyExtensionDataRef extends UNewOutput ? T['output'] : UNewOutput; + inputs: T['inputs'] & TExtraInputs; + kind: T['kind']; + namespace: undefined; + name: string | undefined extends TNewName ? T['name'] : TNewName; + }>; + /** @deprecated namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. */ + makeWithOverrides< + TNewNamespace extends string | undefined, + TNewName extends string | undefined, + TExtensionConfigSchema extends { + [key in string]: (zImpl: typeof z) => z.ZodType; + }, + UFactoryOutput extends ExtensionDataValue, + UNewOutput extends AnyExtensionDataRef, + TExtraInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + >(args: { + namespace: TNewNamespace; name?: TNewName; attachTo?: { id: string; input: string }; disabled?: boolean; @@ -224,6 +324,102 @@ export interface ExtensionBlueprint< * * @public */ +export function createExtensionBlueprint< + TParams extends object, + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends undefined = undefined, + TName extends string | undefined = undefined, + TDataRefs extends { [name in string]: AnyExtensionDataRef } = never, +>( + options: CreateExtensionBlueprintOptions< + TKind, + undefined, + TName, + TParams, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput, + TDataRefs + >, +): ExtensionBlueprint<{ + kind: TKind; + namespace: undefined; + name: TName; + params: TParams; + output: UOutput; + 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; +}>; +/** + * @public + * @deprecated the namespace is no longer required, you can safely remove this option and it will default to the `pluginId`. It will be removed in a future release. + */ +export function createExtensionBlueprint< + TParams extends object, + UOutput extends AnyExtensionDataRef, + TInputs extends { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }, + TConfigSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }, + UFactoryOutput extends ExtensionDataValue, + TKind extends string, + TNamespace extends string | undefined = undefined, + TName extends string | undefined = undefined, + TDataRefs extends { [name in string]: AnyExtensionDataRef } = never, +>( + options: CreateExtensionBlueprintOptions< + TKind, + TNamespace, + TName, + TParams, + UOutput, + TInputs, + TConfigSchema, + UFactoryOutput, + TDataRefs + >, +): ExtensionBlueprint<{ + kind: TKind; + namespace: TNamespace; + name: TName; + params: TParams; + output: UOutput; + 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; +}>; export function createExtensionBlueprint< TParams extends object, UOutput extends AnyExtensionDataRef, diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts index aacc34ccf0..e3fa8b6682 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -15,10 +15,10 @@ */ import { - ExtensionDefinition, InternalExtensionDefinition, toInternalExtensionDefinition, -} from './createExtension'; +} from '@internal/frontend'; +import { ExtensionDefinition } from './createExtension'; import { Extension, resolveExtensionDefinition, diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 03c843543d..adcd2b0867 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -15,10 +15,10 @@ */ import { - ExtensionDefinition, InternalExtensionDefinition, toInternalExtensionDefinition, -} from './createExtension'; +} from '@internal/frontend'; +import { ExtensionDefinition } from './createExtension'; import { Extension, ResolveExtensionId, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 870fb258f5..a377cde2b9 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -19,7 +19,6 @@ import { ExtensionDefinition, ExtensionDefinitionParameters, ResolvedExtensionInputs, - toInternalExtensionDefinition, } from './createExtension'; import { PortableSchema } from '../schema'; import { ExtensionInput } from './createExtensionInput'; @@ -27,6 +26,7 @@ import { AnyExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; +import { toInternalExtensionDefinition } from '@internal/frontend'; /** @public */ export interface Extension { @@ -149,6 +149,7 @@ export function resolveExtensionDefinition< override: _skip2, ...rest } = internalDefinition; + const namespace = internalDefinition.namespace ?? context?.namespace; const namePart = diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 04cc025a74..8a5192c940 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/frontend-test-utils +## 0.2.0-next.2 + +### Patch Changes + +- 2a61422: The extension tester will no longer unconditionally enable any additional extensions that have been added. +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 043d7cd: Internal refactor +- Updated dependencies + - @backstage/test-utils@1.6.0-next.1 + - @backstage/plugin-app@0.1.0-next.2 + - @backstage/frontend-app-api@0.9.0-next.2 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.2.0-next.1 + +### Patch Changes + +- 948d431: Removing deprecated `namespace` parameter in favour of `pluginId` instead +- Updated dependencies + - @backstage/frontend-app-api@0.9.0-next.1 + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/plugin-app@0.1.0-next.1 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.6.0-next.0 + - @backstage/types@1.1.1 + ## 0.2.0-next.0 ### Minor Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index a84d296c55..7fb97d8292 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.2.0-next.0", + "version": "0.2.0-next.2", "backstage": { "role": "web-library" }, @@ -36,7 +36,9 @@ "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-app": "workspace:^", "@backstage/test-utils": "workspace:^", - "@backstage/types": "workspace:^" + "@backstage/types": "workspace:^", + "@backstage/version-bridge": "workspace:^", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", @@ -44,7 +46,7 @@ "@types/react": "*" }, "peerDependencies": { - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "react": "^16.13.1 || ^17.0.0 || ^18.0.0", "react-router-dom": "6.0.0-beta.0 || ^6.3.0" } diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 582bbeed00..a6265ae3ac 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -30,7 +30,6 @@ const stringDataRef = createExtensionDataRef().with({ describe('createExtensionTester', () => { it('should return the correct dataRef when called', () => { const extension = createExtension({ - namespace: 'test', attachTo: { id: 'ignored', input: 'ignored' }, output: [stringDataRef], factory: () => [stringDataRef('test-text')], @@ -43,7 +42,6 @@ describe('createExtensionTester', () => { it('should throw an error if trying to access an instance not provided to the tester', () => { const extension = createExtension({ - namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, output: [stringDataRef], @@ -51,7 +49,6 @@ describe('createExtensionTester', () => { }); const extension2 = createExtension({ - namespace: 'test', name: 'e2', attachTo: { id: 'ignored', input: 'ignored' }, output: [stringDataRef], @@ -61,13 +58,12 @@ describe('createExtensionTester', () => { const tester = createExtensionTester(extension); expect(() => tester.query(extension2)).toThrow( - "Extension with ID 'test/e2' not found, please make sure it's added to the tester", + "Extension with ID 'e2' not found, please make sure it's added to the tester", ); }); it('should throw an error if trying to access an instance which is not part of the tree', () => { const extension = createExtension({ - namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, output: [stringDataRef], @@ -75,7 +71,6 @@ describe('createExtensionTester', () => { }); const extension2 = createExtension({ - namespace: 'test', name: 'e2', attachTo: { id: 'ignored', input: 'ignored' }, output: [stringDataRef], @@ -85,7 +80,7 @@ describe('createExtensionTester', () => { const tester = createExtensionTester(extension).add(extension2); expect(() => tester.query(extension2)).toThrow( - "Extension with ID 'test/e2' has not been instantiated, because it is not part of the test subject's extension tree", + "Extension with ID 'e2' has not been instantiated, because it is not part of the test subject's extension tree", ); }); @@ -99,7 +94,6 @@ describe('createExtensionTester', () => { }); const extension = createExtension({ - namespace: 'test', name: 'e1', attachTo: { id: 'ignored', input: 'ignored' }, output: [stringDataRef, internalRef.optional()], @@ -129,7 +123,6 @@ describe('createExtensionTester', () => { }); const extension = createExtension({ - namespace: 'test', name: 'e1', inputs: { ignored: createExtensionInput([stringDataRef]), @@ -140,9 +133,8 @@ describe('createExtensionTester', () => { }); const extraExtension = createExtension({ - namespace: 'test', name: 'e2', - attachTo: { id: 'test/e1', input: 'ignored' }, + attachTo: { id: 'e1', input: 'ignored' }, output: [stringDataRef, internalRef.optional()], factory: () => [stringDataRef('test-text')], }); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index f9c1b3cb89..5eda2d6e11 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -29,8 +29,6 @@ import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/createExtension'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveAppTree } from '../../../frontend-app-api/src/tree/resolveAppTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveAppNodeSpecs'; @@ -39,6 +37,7 @@ import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/insta // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig'; import { TestApiRegistry } from '@backstage/test-utils'; +import { toInternalExtensionDefinition } from '@internal/frontend'; /** @public */ export class ExtensionQuery { @@ -213,11 +212,17 @@ export class ExtensionTester { const [subject, ...rest] = this.#extensions; const extensionsConfig: JsonArray = [ - ...rest.map(extension => ({ - [extension.id]: { - config: extension.config, - }, - })), + ...rest.flatMap(extension => + extension.config + ? [ + { + [extension.id]: { + config: extension.config, + }, + }, + ] + : [], + ), { [subject.id]: { config: subject.config, diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx index fae67a260c..e53566a118 100644 --- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -25,7 +25,6 @@ import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { createExtension, - createExtensionOverrides, ExtensionDefinition, coreExtensionData, RouteRef, @@ -33,6 +32,7 @@ import { IconComponent, RouterBlueprint, NavItemBlueprint, + createFrontendPlugin, } from '@backstage/frontend-plugin-api'; import appPlugin from '@backstage/plugin-app'; @@ -136,7 +136,6 @@ export function renderInTestApp( ): RenderResult { const extensions: Array = [ createExtension({ - namespace: 'test', attachTo: { id: 'app/routes', input: 'routes' }, output: [coreExtensionData.reactElement, coreExtensionData.routePath], factory: () => { @@ -147,7 +146,6 @@ export function renderInTestApp( }, }), RouterBlueprint.make({ - namespace: 'test', params: { Component: ({ children }) => {children}, }, @@ -182,10 +180,11 @@ export function renderInTestApp( } const features: FrontendFeature[] = [ - appPluginOverride, - createExtensionOverrides({ + createFrontendPlugin({ + id: 'test', extensions, }), + appPluginOverride, ]; if (options?.features) { diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index a5bf99ba4d..ce58c5165f 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/integration-react +## 1.1.31-next.0 + +### Patch Changes + +- 8a9d797: Remove unnecessary broad permissions from Gitlab `SCMAuth` + + Newer versions of Gitlab (after 2019) do not require the broad api permissions to write to repos. + +- Updated dependencies + - @backstage/integration@1.15.0-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/config@1.2.0 + ## 1.1.30 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 0ae77a9bd2..fb4bb95190 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.1.30", + "version": "1.1.31-next.0", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration-react/src/api/ScmAuth.test.ts b/packages/integration-react/src/api/ScmAuth.test.ts index d252b95b85..e63ce2844c 100644 --- a/packages/integration-react/src/api/ScmAuth.test.ts +++ b/packages/integration-react/src/api/ScmAuth.test.ts @@ -105,7 +105,7 @@ describe('ScmAuth', () => { additionalScope: { repoWrite: true }, }), ).resolves.toMatchObject({ - token: 'read_user read_api read_repository write_repository api', + token: 'read_user read_api read_repository write_repository', }); const azureAuth = ScmAuth.forAzure(mockAuthApi); diff --git a/packages/integration-react/src/api/ScmAuth.ts b/packages/integration-react/src/api/ScmAuth.ts index a22b15e400..6257915c8c 100644 --- a/packages/integration-react/src/api/ScmAuth.ts +++ b/packages/integration-react/src/api/ScmAuth.ts @@ -162,7 +162,7 @@ export class ScmAuth implements ScmAuthApi { * * If the additional `repoWrite` permission is requested, these scopes are added: * - * `write_repository api` + * `write_repository` */ static forGitlab( gitlabAuthApi: OAuthApi, @@ -173,7 +173,7 @@ export class ScmAuth implements ScmAuthApi { const host = options?.host ?? 'gitlab.com'; return new ScmAuth('gitlab', gitlabAuthApi, host, { default: ['read_user', 'read_api', 'read_repository'], - repoWrite: ['write_repository', 'api'], + repoWrite: ['write_repository'], }); } diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 9e8fef7af8..ebeb684ebc 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/integration +## 1.15.0-next.0 + +### Minor Changes + +- 1573014: The Gerrit integration can now resolve Gitiles urls that point to the following + refs: + + - HEAD + - A SHA + - Tag + - Branch + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 1.14.0 ### Minor Changes diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index cf4b380ad9..e9b67bb6c1 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -766,6 +766,18 @@ export function parseGiteaUrl( path: string; }; +// @public +export function parseGitilesUrlRef( + config: GerritIntegrationConfig, + url: string, +): { + project: string; + path: string; + ref: string; + refType: 'sha' | 'branch' | 'tag' | 'head'; + basePath: string; +}; + // @public export function parseHarnessUrl( config: HarnessIntegrationConfig, diff --git a/packages/integration/package.json b/packages/integration/package.json index 83c6e4ce8b..2a98a9afcc 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.14.0", + "version": "1.15.0-next.0", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/integration/src/gerrit/GerritIntegration.test.ts b/packages/integration/src/gerrit/GerritIntegration.test.ts index 1878744f68..2cfc748550 100644 --- a/packages/integration/src/gerrit/GerritIntegration.test.ts +++ b/packages/integration/src/gerrit/GerritIntegration.test.ts @@ -83,11 +83,10 @@ describe('GerritIntegration', () => { }); describe('resolves with a relative url', () => { - it('works for valid urls', () => { - const integration = new GerritIntegration({ - host: 'gerrit-review.example.com', - } as any); - + const integration = new GerritIntegration({ + host: 'gerrit-review.example.com', + } as any); + it('works for valid urls pointing to a branch', () => { expect( integration.resolveUrl({ url: './skeleton', @@ -97,15 +96,24 @@ describe('GerritIntegration', () => { 'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/heads/master/skeleton', ); }); + it('works for urls pointing to a tag', () => { + expect( + integration.resolveUrl({ + url: './skeleton.yaml', + base: 'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/src/template.yaml', + }), + ).toBe( + 'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/src/skeleton.yaml', + ); + }); }); describe('resolves with an absolute url', () => { - it('works for valid urls', () => { - const integration = new GerritIntegration({ - host: 'gerrit-review.example.com', - gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', - } as any); - + const integration = new GerritIntegration({ + host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', + } as any); + it('works for valid urls pointing to a branch', () => { expect( integration.resolveUrl({ url: '/catalog-info.yaml', @@ -115,6 +123,16 @@ describe('GerritIntegration', () => { 'https://gerrit-review.example.com/gitiles/repo/+/refs/heads/master/catalog-info.yaml', ); }); + it('works for urls pointing to a tag', () => { + expect( + integration.resolveUrl({ + url: '/skeleton.yaml', + base: 'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/src/template.yaml', + }), + ).toBe( + 'https://gerrit-review.example.com/gerrit/plugins/repo/+/refs/tags/v.1.2.3/skeleton.yaml', + ); + }); }); it('resolve edit URL', () => { diff --git a/packages/integration/src/gerrit/GerritIntegration.ts b/packages/integration/src/gerrit/GerritIntegration.ts index 6ea426e407..7e2e757c6b 100644 --- a/packages/integration/src/gerrit/GerritIntegration.ts +++ b/packages/integration/src/gerrit/GerritIntegration.ts @@ -20,7 +20,7 @@ import { GerritIntegrationConfig, readGerritIntegrationConfigs, } from './config'; -import { parseGerritGitilesUrl, buildGerritGitilesUrl } from './core'; +import { parseGitilesUrlRef } from './core'; /** * A Gerrit based integration. @@ -60,8 +60,8 @@ export class GerritIntegration implements ScmIntegration { const { url, base, lineNumber } = options; let updated; if (url.startsWith('/')) { - const { branch, project } = parseGerritGitilesUrl(this.config, base); - return buildGerritGitilesUrl(this.config, project, branch, url); + const { basePath } = parseGitilesUrlRef(this.config, base); + return basePath + url; } if (url) { updated = new URL(url, base); diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts index 35632c654e..c031612c40 100644 --- a/packages/integration/src/gerrit/core.test.ts +++ b/packages/integration/src/gerrit/core.test.ts @@ -26,6 +26,7 @@ import { getGerritCloneRepoUrl, getGerritRequestOptions, parseGerritJsonResponse, + parseGitilesUrlRef, parseGerritGitilesUrl, getGerritFileContentsApiUrl, } from './core'; @@ -148,8 +149,129 @@ describe('gerrit core', () => { ).toBeUndefined(); }); }); - - describe('parseGitilesUrl', () => { + describe('parseGitilesUrlRef', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + gitilesBaseUrl: 'https://gerrit.googlesource.com', + }; + it('can parse a gitiles urls that points to specific sha.', () => { + const gitUrl = parseGitilesUrlRef( + config, + 'https://gerrit.googlesource.com/modules/cached-refdb/+/157f862803d45b9d269f0e390f88aece1ded51e8/Jenkinsfile', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.googlesource.com/modules/cached-refdb/+/157f862803d45b9d269f0e390f88aece1ded51e8', + path: 'Jenkinsfile', + project: 'modules/cached-refdb', + ref: '157f862803d45b9d269f0e390f88aece1ded51e8', + refType: 'sha', + }); + }); + it('can parse gitiles urls that points to tags.', () => { + const gitUrl = parseGitilesUrlRef( + config, + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/tags/v3.5.6/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/tags/v3.5.6', + path: 'src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java', + project: 'modules/events-broker', + ref: 'v3.5.6', + refType: 'tag', + }); + }); + it('can parse gitiles urls that points to HEAD.', () => { + const gitUrl = parseGitilesUrlRef( + config, + 'https://gerrit.googlesource.com/modules/events-broker/+/HEAD/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.googlesource.com/modules/events-broker/+/HEAD', + path: 'src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApi.java', + project: 'modules/events-broker', + ref: 'HEAD', + refType: 'head', + }); + }); + it('can parse gitiles urls that points to HEAD without path.', () => { + const gitUrl = parseGitilesUrlRef( + config, + 'https://gerrit.googlesource.com/modules/events-broker/+/HEAD', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.googlesource.com/modules/events-broker/+/HEAD', + path: '/', + project: 'modules/events-broker', + ref: 'HEAD', + refType: 'head', + }); + }); + it('can parse gitiles urls that points to branches.', () => { + const gitUrl = parseGitilesUrlRef( + config, + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master/src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApiModule.java', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master', + path: 'src/main/java/com/gerritforge/gerrit/eventbroker/BrokerApiModule.java', + project: 'modules/events-broker', + ref: 'master', + refType: 'branch', + }); + }); + it('can parse gitiles urls that points directly to a branch without a path.', () => { + const gitUrl = parseGitilesUrlRef( + config, + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master', + path: '/', + project: 'modules/events-broker', + ref: 'master', + refType: 'branch', + }); + }); + it('can parse gitiles urls that points to the repo root.', () => { + const gitUrl = parseGitilesUrlRef( + config, + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master/', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.googlesource.com/modules/events-broker/+/refs/heads/master', + path: '/', + project: 'modules/events-broker', + ref: 'master', + refType: 'branch', + }); + }); + it('can parse a valid authenticated gitiles url.', () => { + const gitilesConfig: GerritIntegrationConfig = { + host: 'gerrit.com', + gitilesBaseUrl: 'https://gerrit.com/gitiles', + }; + const gitUrl = parseGitilesUrlRef( + gitilesConfig, + 'https://gerrit.com/a/gitiles/web/project/+/refs/heads/master/README.md', + ); + expect(gitUrl).toEqual({ + basePath: + 'https://gerrit.com/a/gitiles/web/project/+/refs/heads/master', + path: 'README.md', + project: 'web/project', + ref: 'master', + refType: 'branch', + }); + }); + }); + describe('parseGerritGitilesUrl', () => { it('can parse a valid gitiles urls.', () => { const config: GerritIntegrationConfig = { host: 'gerrit.com', diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts index b3bdbc30e4..e50ce97bbb 100644 --- a/packages/integration/src/gerrit/core.ts +++ b/packages/integration/src/gerrit/core.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { trimStart } from 'lodash'; +import { join, takeWhile, trimEnd, trimStart } from 'lodash'; import { GerritIntegrationConfig } from './config'; const GERRIT_BODY_PREFIX = ")]}'"; @@ -81,6 +81,112 @@ export function parseGerritGitilesUrl( }; } +/** + * Parses Gitiles urls and returns the following: + * + * - The project + * - The type of ref. I.e: branch name, SHA, HEAD or tag. + * - The file path from the repo root. + * - The base path as the path that points to the repo root. + * + * Supported types of gitiles urls that point to: + * + * - Branches + * - Tags + * - A commit SHA + * - HEAD + * + * @param config - A Gerrit provider config. + * @param url - An url to a file or folder in Gitiles. + * @public + */ +export function parseGitilesUrlRef( + config: GerritIntegrationConfig, + url: string, +): { + project: string; + path: string; + ref: string; + refType: 'sha' | 'branch' | 'tag' | 'head'; + basePath: 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 = trimStart( + urlParse.pathname + .substring(urlParse.pathname.startsWith('/a/') ? 2 : 0) + .replace(baseUrlParse.pathname, ''), + '/', + ); + + // Find the project by taking everything up to "/+/". + const parts = urlPath.split('/').filter(p => !!p); + const projectParts = takeWhile(parts, p => p !== '+'); + if (projectParts.length === 0) { + throw new Error(`Unable to parse gitiles url: ${url}`); + } + // Also remove the "+" after the project. + const rest = parts.slice(projectParts.length + 1); + const project = join(projectParts, '/'); + + // match /+/HEAD/ + if (rest.length > 0 && rest[0] === 'HEAD') { + const ref = rest.shift()!; + const path = join(rest, '/'); + return { + project, + ref, + refType: 'head' as const, + path: path || '/', + basePath: trimEnd(url.replace(path, ''), '/'), + }; + } + // match /+// + if (rest.length > 0 && rest[0].length === 40) { + const ref = rest.shift()!; + const path = join(rest, '/'); + return { + project, + ref, + refType: 'sha' as const, + path: path || '/', + basePath: trimEnd(url.replace(path, ''), '/'), + }; + } + const remainingPath = join(rest, '/'); + // Regexp for matching "refs/tags/" or "refs/heads//" + const refsRegexp = /^refs\/(?heads|tags)\/(?.*?)(\/|$)/; + const result = refsRegexp.exec(remainingPath); + if (result) { + const matchString = result[0]; + let refType; + const { refsReference, ref } = result.groups || {}; + const path = remainingPath.replace(matchString, ''); + switch (refsReference) { + case 'heads': + refType = 'branch' as const; + break; + case 'tags': + refType = 'tag' as const; + break; + default: + throw new Error(`Unable to parse gitiles url: ${url}`); + } + return { + project, + ref, + refType, + path: path || '/', + basePath: trimEnd(url.replace(path, ''), '/'), + }; + } + throw new Error(`Unable to parse gitiles : ${url}`); +} + /** * Build a Gerrit Gitiles url that targets a specific path. * diff --git a/packages/integration/src/gerrit/index.ts b/packages/integration/src/gerrit/index.ts index 6c6295f7a3..f2e5934c95 100644 --- a/packages/integration/src/gerrit/index.ts +++ b/packages/integration/src/gerrit/index.ts @@ -27,6 +27,7 @@ export { getGerritRequestOptions, parseGerritJsonResponse, parseGerritGitilesUrl, + parseGitilesUrlRef, } from './core'; export type { GerritIntegrationConfig } from './config'; diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 228886efc1..361c1d5727 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/repo-tools +## 0.9.7-next.2 + +### Patch Changes + +- 1a8837e: Avoid generating API reports for packages with `backstage.inline` set. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/cli-node@0.2.8-next.0 + - @backstage/config-loader@1.9.1-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## 0.9.7-next.1 + +### Patch Changes + +- 5c4aa2f: Updated dependency `@useoptic/openapi-utilities` to `^0.55.0`. +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + ## 0.9.7-next.0 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 5713659568..ac51827008 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.9.7-next.0", + "version": "0.9.7-next.2", "publishConfig": { "access": "public" }, @@ -60,7 +60,7 @@ "@stoplight/spectral-rulesets": "^1.18.0", "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "^14.0.0", - "@useoptic/openapi-utilities": "^0.54.8", + "@useoptic/openapi-utilities": "^0.55.0", "chalk": "^4.0.0", "codeowners-utils": "^1.0.2", "command-exists": "^1.2.9", diff --git a/packages/repo-tools/src/commands/api-reports/api-extractor.ts b/packages/repo-tools/src/commands/api-reports/api-extractor.ts index 122f04cc7e..371f122713 100644 --- a/packages/repo-tools/src/commands/api-reports/api-extractor.ts +++ b/packages/repo-tools/src/commands/api-reports/api-extractor.ts @@ -1232,6 +1232,12 @@ export async function categorizePackageDirs(packageDirs: string[]) { if (!role) { return; // Ignore packages without roles } + // TODO(Rugvip): Inlined packages are ignored because we can't handle @internal exports + // gracefully, and we don't want to have to mark all exports @public etc. + // It would be good if we could include these packages though. + if (pkgJson?.backstage?.inline) { + return; + } if (role === 'cli') { cliPackageDirs.push(dir); } else if (role !== 'frontend' && role !== 'backend') { diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index ea42ae4cc0..027bb0642e 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,43 @@ # techdocs-cli-embedded-app +## 0.2.100-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-techdocs@1.10.9-next.2 + - @backstage/plugin-catalog@1.23.0-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/app-defaults@1.5.11-next.1 + - @backstage/test-utils@1.6.0-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-techdocs-react@1.2.8-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + +## 0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.1-next.1 + - @backstage/core-components@0.14.11-next.0 + - @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/test-utils@1.6.0-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-techdocs@1.10.9-next.1 + - @backstage/plugin-techdocs-react@1.2.8-next.1 + ## 0.2.100-next.0 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 54844b882d..5956f24128 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.100-next.0", + "version": "0.2.100-next.2", "private": true, "backstage": { "role": "frontend" @@ -38,7 +38,7 @@ "@backstage/cli": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/react": "*", "@types/react-dom": "*", diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index c3b4be91f8..934c5c65da 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,27 @@ # @techdocs/cli +## 1.8.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/plugin-techdocs-node@1.12.11-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## 1.8.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + ## 1.8.19-next.0 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index c0af6ceaed..1c585090cd 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.19-next.0", + "version": "1.8.19-next.2", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 29d38987d7..a46e1d374a 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/test-utils +## 1.6.0-next.1 + +### Patch Changes + +- 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-app-api@1.14.3-next.0 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1 + ## 1.6.0-next.0 ### Minor Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 16d1609c66..e6afeb9744 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.6.0-next.0", + "version": "1.6.0-next.1", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" @@ -69,7 +69,7 @@ "msw": "^1.0.0" }, "peerDependencies": { - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "react": "^16.13.1 || ^17.0.0 || ^18.0.0", "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", "react-router-dom": "6.0.0-beta.0 || ^6.3.0" diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index 3a55389b74..6f9361f39b 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -63,6 +63,8 @@ const mockIcons = { help: MockIcon, user: MockIcon, warning: MockIcon, + star: MockIcon, + unstarred: MockIcon, }; const ErrorBoundaryFallback = ({ error }: { error: Error }) => { diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 94d85c535a..f06206a0af 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.5.7-next.0 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. + ## 0.5.6 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index b199cb3061..a64e033c95 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.5.6", + "version": "0.5.7-next.0", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" @@ -44,7 +44,7 @@ "@backstage/cli": "workspace:^", "@mui/styles": "^5.14.18", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@types/react": "^16.13.1 || ^17.0.0" }, "peerDependencies": { diff --git a/packages/version-bridge/CHANGELOG.md b/packages/version-bridge/CHANGELOG.md index 86dadeac65..47e02694f4 100644 --- a/packages/version-bridge/CHANGELOG.md +++ b/packages/version-bridge/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/version-bridge +## 1.0.9-next.0 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. + ## 1.0.8 ### Patch Changes diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json index c2bb341d49..aa7f38e593 100644 --- a/packages/version-bridge/package.json +++ b/packages/version-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/version-bridge", - "version": "1.0.8", + "version": "1.0.9-next.0", "description": "Utilities used by @backstage packages to support multiple concurrent versions", "backstage": { "role": "web-library" @@ -41,7 +41,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0" + "@testing-library/react": "^16.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 7c67bea51e..cdf450e1a4 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-api-docs +## 0.11.9-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 8a474f2: Updating docs to use `createFrontendModule` instead +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/plugin-catalog@1.23.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.11.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog@1.22.1-next.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + ## 0.11.9-next.0 ### Patch Changes diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index eb84ef8c27..07f4d5b6bc 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -203,21 +203,23 @@ The apis nav item icon can only be changed by overriding the extension, as the i Here is an example overriding the nav item extension with a custom icon component: ```tsx -import { createExtensionOverrides, createNavItemExtension } from '@backstage/backstage-plugin-api'; +import { + createFrontendModule, + createNavItemExtension, +} from '@backstage/backstage-plugin-api'; import { MyCustomApiDocsIcon } from './components'; -export default createExtensionOverrides( +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createNavItemExtension({ - // These namespace is necessary so the system knows that this extension will override the default nav item provided by the 'api-docs' plugin - namespace: 'api-docs', // It's your choice whether to use the original extension's title or a different one title: 'APIs', // Setting a custom icon component icon: MyCustomApiDocsIcon, - }) - ] -); + }), + ], +}); ``` For more information about where to place extension overrides, see the official [documentation](https://backstage.io/docs/frontend-system/architecture/extension-overrides). @@ -273,24 +275,27 @@ The explorer page implementation can be [overridden](https://backstage.io/docs/f Here is an example overriding the APIs Explorer page component: ```tsx -import { createExtensionOverrides, createPageExtension } from '@backstage/backstage-plugin-api'; +import { + createFrontendModule, + createPageExtension, +} from '@backstage/backstage-plugin-api'; import { convertLegacyRouteRef } from '@backstage/core-compat-api'; -export default createExtensionOverrides( +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createPageExtension({ - // These namespace is necessary so the system knows that this extension will override the default explorer page provided by the 'api-docs' plugin - namespace: 'api-docs', // Ommitting name since we are overriding a plugin index page // It's up to you whether to use the original default path or not, but links that are hardcoded to the default path won't work if you change it defaultPath: '/api-docs', // Associating the page with a different route ref may result in the sidebar item or external plugin route pointing to an unreachable page routeRef: convertLegacyRouteRef(rootRoute), // Custom page components are loaded here - loader: () => import('./components').then(m => ) - }) - ] -); + loader: () => + import('./components').then(m => ), + }), + ], +}); ``` #### Apis Entities Cards @@ -356,14 +361,14 @@ app: Use extension overrides for completely re-implementing the has apis entity card extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityCardExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'has-apis' entity card extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom card component loader: () => @@ -429,14 +434,14 @@ app: Use extension overrides for completely re-implementing the has apis entity card extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityCardExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'definition' entity card extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom card component loader: () => @@ -502,14 +507,14 @@ app: Use extension overrides for completely re-implementing the has apis entity card extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityCardExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'provided-apis' entity card extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom card component loader: () => @@ -575,14 +580,14 @@ app: Use extension overrides for completely re-implementing the has apis entity card extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityCardExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'consumed-apis' entity card extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom card component loader: () => @@ -648,14 +653,14 @@ app: Use extension overrides for completely re-implementing the has apis entity card extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityCardExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'providing-components' entity card extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom card component loader: () => @@ -723,14 +728,14 @@ app: Use extension overrides for completely re-implementing the has apis entity card extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityCardExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'consuming-components' entity card extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom card component loader: () => @@ -812,14 +817,14 @@ app: Use extension overrides for completely re-implementing the has apis entity card extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityContentExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'definition' entity content extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom content component loader: () => @@ -892,14 +897,14 @@ app: Use extension overrides for completely re-implementing the apis entity content extension: ```tsx -import { createExtensionOverrides } from '@backstage/backstage-plugin-api'; +import { createFrontendModule } from '@backstage/backstage-plugin-api'; import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createEntityContentExtension({ - // These namespace and name necessary so the system knows that this extension will override the default 'apis' entity content extension provided by the 'api-docs' plugin - namespace: 'api-docs', + // 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', // Returing a custom content component loader: () => @@ -929,7 +934,7 @@ This is an example with a made-up renderer for SQL schemas: ```tsx import { - createExtensionOverrides, + createFrontendModule, createApiExtenion, createApiFactory, } from '@backstage/frontend-plugin-api'; @@ -941,7 +946,8 @@ import { } from '@backstage/plugin-api-docs'; import { SqlRenderer } from '...'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createApiExtenion({ factory: createApiFactory({ @@ -982,7 +988,7 @@ Override the config api to configure a [`requestInterceptor` for Swagger UI](htt ```tsx import { - createExtensionOverrides, + createFrontendModule, createApiExtenion, createApiFactory, } from '@backstage/frontend-plugin-api'; @@ -993,7 +999,8 @@ import { } from '@backstage/plugin-api-docs'; import { ApiEntity } from '@backstage/catalog-model'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createApiExtenion({ factory: createApiFactory({ @@ -1043,7 +1050,7 @@ If you want to limit the HTTP methods available for the `Try It Out` feature of ```tsx import { - createExtensionOverrides, + createFrontendModule, createApiExtenion, createApiFactory, } from '@backstage/frontend-plugin-api'; @@ -1054,7 +1061,8 @@ import { } from '@backstage/plugin-api-docs'; import { ApiEntity } from '@backstage/catalog-model'; -export default createExtensionOverrides({ +export default createFrontendModule({ + pluginId: 'api-docs', extensions: [ createApiExtenion({ factory: createApiFactory({ diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 7f27a4c6a2..6858d6a81f 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.9-next.0", + "version": "0.11.9-next.2", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", @@ -82,7 +82,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/highlightjs": "^10.1.0", "@types/swagger-ui-react": "^4.18.0" diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index c7e709c1ec..06e6f49387 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-app-backend +## 0.3.74-next.2 + +### Patch Changes + +- 590fb2d: **BREAKING**: The app backend now supports the new `index.html.tmpl` output from `@backstage/cli`. If available, the `index.html` will be templated at runtime with the current configuration of the app backend. + + This is marked as a breaking change because you must now supply the app build-time configuration to the backend. This change also affects the public path behavior, where it is no longer necessary to build the app with the correct public path upfront. You now only need to supply a correct `app.baseUrl` to the app backend plugin at runtime. + + An effect that this change has is that the `index.html` will now contain and present the frontend configuration in an easily readable way, which can aid in debugging. This data was always available in the frontend, but it was injected and hidden in the static bundle. + + This templating behavior is enabled by default, but it can be disabled by setting the `app.disableConfigInjection` configuration option to `true`. + +- 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-loader@1.9.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.2 + +## 0.3.74-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/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.25-next.1 + ## 0.3.74-next.0 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 90c85f8b63..606d5adea6 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.74-next.0", + "version": "0.3.74-next.2", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", @@ -84,7 +84,7 @@ "@backstage/types": "workspace:^", "@types/supertest": "^2.0.8", "node-fetch": "^2.7.0", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/app-backend/src/lib/config/index.ts b/plugins/app-backend/src/lib/config/index.ts new file mode 100644 index 0000000000..2e685578d0 --- /dev/null +++ b/plugins/app-backend/src/lib/config/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { injectConfig } from './injectConfig'; +export { readFrontendConfig } from './readFrontendConfig'; diff --git a/plugins/app-backend/src/lib/config/injectConfig.ts b/plugins/app-backend/src/lib/config/injectConfig.ts new file mode 100644 index 0000000000..9d63d219f4 --- /dev/null +++ b/plugins/app-backend/src/lib/config/injectConfig.ts @@ -0,0 +1,36 @@ +/* + * 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 { injectConfigIntoHtml } from './injectConfigIntoHtml'; +import { injectConfigIntoStatic } from './injectConfigIntoStatic'; +import { InjectOptions } from './types'; + +/** + * Injects configs into the app bundle, replacing any existing injected config. + * @internal + */ +export async function injectConfig( + options: InjectOptions, +): Promise { + // In order to minimize the potential impact when rolling out the new config + // injection, we use both methods for a few releases. This allows the frontend + // app to be behind the backend by a version or two, but temporarily increases + // config injection overhead. + // TODO(Rugvip): After the 1.32 release we can stop calling the static injection if the HTML one is successful + await injectConfigIntoHtml(options); + + return injectConfigIntoStatic(options); +} diff --git a/plugins/app-backend/src/lib/config/injectConfigIntoHtml.test.ts b/plugins/app-backend/src/lib/config/injectConfigIntoHtml.test.ts new file mode 100644 index 0000000000..f4f2655f1c --- /dev/null +++ b/plugins/app-backend/src/lib/config/injectConfigIntoHtml.test.ts @@ -0,0 +1,73 @@ +/* + * 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 { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; +import { injectConfigIntoHtml } from './injectConfigIntoHtml'; + +describe('injectConfigIntoHtml', () => { + const mockDir = createMockDirectory(); + + const baseOptions = { + appConfigs: [], + rootDir: mockDir.path, + staticDir: 'ignored', + logger: mockServices.logger.mock(), + }; + + beforeEach(() => { + mockDir.clear(); + }); + + it('should template html', async () => { + mockDir.setContent({ + 'index.html.tmpl': "<%= config.getNumber('x') %>", + }); + await injectConfigIntoHtml({ + ...baseOptions, + appConfigs: [{ context: 'mock', data: { x: 1 } }], + }); + expect(mockDir.content()).toMatchObject({ + 'index.html': '1', + }); + }); + + it('should inject config', async () => { + mockDir.setContent({ + 'index.html.tmpl': '', + }); + await injectConfigIntoHtml({ + ...baseOptions, + appConfigs: [{ context: 'mock', data: { x: 1 } }], + }); + expect(mockDir.content()).toMatchObject({ + 'index.html': ` + +`, + }); + }); +}); diff --git a/plugins/app-backend/src/lib/config/injectConfigIntoHtml.ts b/plugins/app-backend/src/lib/config/injectConfigIntoHtml.ts new file mode 100644 index 0000000000..604b6a8711 --- /dev/null +++ b/plugins/app-backend/src/lib/config/injectConfigIntoHtml.ts @@ -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 fs from 'fs-extra'; +import { resolve as resolvePath } from 'path'; +import { InjectOptions } from './types'; +import compileTemplate from 'lodash/template'; +import { Config, ConfigReader } from '@backstage/config'; + +const HTML_TEMPLATE_NAME = 'index.html.tmpl'; + +/** @internal */ +export async function injectConfigIntoHtml( + options: InjectOptions, +): Promise { + const { rootDir, appConfigs } = options; + + const templatePath = resolvePath(rootDir, HTML_TEMPLATE_NAME); + + if (!(await fs.exists(templatePath))) { + return false; + } + + const templateContent = await fs.readFile( + resolvePath(rootDir, HTML_TEMPLATE_NAME), + 'utf8', + ); + + const config = ConfigReader.fromConfigs(appConfigs); + + const templateSource = compileTemplate(templateContent, { + interpolate: /<%=([\s\S]+?)%>/g, + }); + + const publicPath = resolvePublicPath(config); + const indexHtmlContent = templateSource({ + config, + publicPath, + }); + + const indexHtmlContentWithConfig = indexHtmlContent.replace( + '', + ` + +`, + ); + + await fs.writeFile( + resolvePath(rootDir, 'index.html'), + indexHtmlContentWithConfig, + 'utf8', + ); + + return true; +} + +export function resolvePublicPath(config: Config) { + const baseUrl = new URL( + config.getOptionalString('app.baseUrl') ?? '/', + 'http://localhost:7007', + ); + return baseUrl.pathname.replace(/\/+$/, ''); +} diff --git a/plugins/app-backend/src/lib/config.test.ts b/plugins/app-backend/src/lib/config/injectConfigIntoStatic.test.ts similarity index 91% rename from plugins/app-backend/src/lib/config.test.ts rename to plugins/app-backend/src/lib/config/injectConfigIntoStatic.test.ts index 34f744d5b8..cd5b4dcae2 100644 --- a/plugins/app-backend/src/lib/config.test.ts +++ b/plugins/app-backend/src/lib/config/injectConfigIntoStatic.test.ts @@ -18,13 +18,14 @@ import { createMockDirectory, mockServices, } from '@backstage/backend-test-utils'; -import { injectConfig } from './config'; +import { injectConfigIntoStatic } from './injectConfigIntoStatic'; -describe('injectConfig', () => { +describe('injectConfigIntoStatic', () => { const mockDir = createMockDirectory(); const baseOptions = { appConfigs: [], + rootDir: 'ignored', staticDir: mockDir.path, logger: mockServices.logger.mock(), }; @@ -42,7 +43,7 @@ describe('injectConfig', () => { 'main.js': '"__APP_INJECTED_RUNTIME_CONFIG__"', }); - await injectConfig(baseOptions); + await injectConfigIntoStatic(baseOptions); expect(mockDir.content()).toEqual({ 'main.js': '/*__APP_INJECTED_CONFIG_MARKER__*/"[]"/*__INJECTED_END__*/', @@ -55,7 +56,7 @@ describe('injectConfig', () => { '({a:"__APP_INJECTED_RUNTIME_CONFIG__",b:"__APP_INJECTED_RUNTIME_CONFIG__"})', }); - await injectConfig(baseOptions); + await injectConfigIntoStatic(baseOptions); expect(mockDir.content()).toEqual({ 'main.js': @@ -71,7 +72,7 @@ describe('injectConfig', () => { 'after.js': 'NO_PLACEHOLDER_HERE', }); - await injectConfig({ + await injectConfigIntoStatic({ ...baseOptions, appConfigs: [{ data: { x: 0 }, context: 'test' }], }); @@ -90,7 +91,7 @@ describe('injectConfig', () => { 'main.js': 'JSON.parse("__APP_INJECTED_RUNTIME_CONFIG__")', }); - await injectConfig({ + await injectConfigIntoStatic({ ...baseOptions, appConfigs: [{ data: { x: 0 }, context: 'test' }], }); @@ -100,7 +101,7 @@ describe('injectConfig', () => { 'JSON.parse(/*__APP_INJECTED_CONFIG_MARKER__*/"[{\\"data\\":{\\"x\\":0},\\"context\\":\\"test\\"}]"/*__INJECTED_END__*/)', }); - await injectConfig({ + await injectConfigIntoStatic({ ...baseOptions, appConfigs: [{ data: { x: 1, y: 2 }, context: 'test' }], }); @@ -117,7 +118,7 @@ describe('injectConfig', () => { '({ a: JSON.parse("__APP_INJECTED_RUNTIME_CONFIG__"), b: JSON.parse("__APP_INJECTED_RUNTIME_CONFIG__") })', }); - await injectConfig({ + await injectConfigIntoStatic({ ...baseOptions, appConfigs: [{ data: { x: 0 }, context: 'test' }], }); @@ -127,7 +128,7 @@ describe('injectConfig', () => { '({ a: JSON.parse(/*__APP_INJECTED_CONFIG_MARKER__*/"[{\\"data\\":{\\"x\\":0},\\"context\\":\\"test\\"}]"/*__INJECTED_END__*/), b: JSON.parse(/*__APP_INJECTED_CONFIG_MARKER__*/"[{\\"data\\":{\\"x\\":0},\\"context\\":\\"test\\"}]"/*__INJECTED_END__*/) })', }); - await injectConfig({ + await injectConfigIntoStatic({ ...baseOptions, appConfigs: [{ data: { x: 1, y: 2 }, context: 'test' }], }); diff --git a/plugins/app-backend/src/lib/config/injectConfigIntoStatic.ts b/plugins/app-backend/src/lib/config/injectConfigIntoStatic.ts new file mode 100644 index 0000000000..29975b0c2a --- /dev/null +++ b/plugins/app-backend/src/lib/config/injectConfigIntoStatic.ts @@ -0,0 +1,61 @@ +/* + * 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 fs from 'fs-extra'; +import { resolve as resolvePath } from 'path'; +import { InjectOptions } from './types'; + +/** + * Injects configs into the app bundle, replacing any existing injected config. + */ +export async function injectConfigIntoStatic( + options: InjectOptions, +): Promise { + const { staticDir, logger, appConfigs } = options; + + const files = await fs.readdir(staticDir); + const jsFiles = files.filter(file => file.endsWith('.js')); + + const escapedData = JSON.stringify(appConfigs).replace(/("|'|\\)/g, '\\$1'); + const injected = `/*__APP_INJECTED_CONFIG_MARKER__*/"${escapedData}"/*__INJECTED_END__*/`; + + for (const jsFile of jsFiles) { + const path = resolvePath(staticDir, jsFile); + + const content = await fs.readFile(path, 'utf8'); + if (content.includes('__APP_INJECTED_RUNTIME_CONFIG__')) { + logger.info(`Injecting env config into ${jsFile}`); + + const newContent = content.replaceAll( + '"__APP_INJECTED_RUNTIME_CONFIG__"', + injected, + ); + await fs.writeFile(path, newContent, 'utf8'); + return path; + } else if (content.includes('__APP_INJECTED_CONFIG_MARKER__')) { + logger.info(`Replacing injected env config in ${jsFile}`); + + const newContent = content.replaceAll( + /\/\*__APP_INJECTED_CONFIG_MARKER__\*\/.*?\/\*__INJECTED_END__\*\//g, + injected, + ); + await fs.writeFile(path, newContent, 'utf8'); + return path; + } + } + logger.info('Env config not injected'); + return undefined; +} diff --git a/plugins/app-backend/src/lib/config.ts b/plugins/app-backend/src/lib/config/readFrontendConfig.ts similarity index 55% rename from plugins/app-backend/src/lib/config.ts rename to plugins/app-backend/src/lib/config/readFrontendConfig.ts index df1146e746..becd1c2f52 100644 --- a/plugins/app-backend/src/lib/config.ts +++ b/plugins/app-backend/src/lib/config/readFrontendConfig.ts @@ -23,69 +23,17 @@ import { loadConfigSchema, readEnvConfig, } from '@backstage/config-loader'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -type InjectOptions = { - appConfigs: AppConfig[]; - // Directory of the static JS files to search for file to inject - staticDir: string; - logger: LoggerService; -}; - -/** - * Injects configs into the app bundle, replacing any existing injected config. - */ -export async function injectConfig( - options: InjectOptions, -): Promise { - const { staticDir, logger, appConfigs } = options; - - const files = await fs.readdir(staticDir); - const jsFiles = files.filter(file => file.endsWith('.js')); - - const escapedData = JSON.stringify(appConfigs).replace(/("|'|\\)/g, '\\$1'); - const injected = `/*__APP_INJECTED_CONFIG_MARKER__*/"${escapedData}"/*__INJECTED_END__*/`; - - for (const jsFile of jsFiles) { - const path = resolvePath(staticDir, jsFile); - - const content = await fs.readFile(path, 'utf8'); - if (content.includes('__APP_INJECTED_RUNTIME_CONFIG__')) { - logger.info(`Injecting env config into ${jsFile}`); - - const newContent = content.replaceAll( - '"__APP_INJECTED_RUNTIME_CONFIG__"', - injected, - ); - await fs.writeFile(path, newContent, 'utf8'); - return path; - } else if (content.includes('__APP_INJECTED_CONFIG_MARKER__')) { - logger.info(`Replacing injected env config in ${jsFile}`); - - const newContent = content.replaceAll( - /\/\*__APP_INJECTED_CONFIG_MARKER__\*\/.*?\/\*__INJECTED_END__\*\//g, - injected, - ); - await fs.writeFile(path, newContent, 'utf8'); - return path; - } - } - logger.info('Env config not injected'); - return undefined; -} - -type ReadOptions = { - env: { [name: string]: string | undefined }; - appDistDir: string; - config: Config; - schema?: ConfigSchema; -}; /** * Read config from environment and process the backend config using the * schema that is embedded in the frontend build. */ -export async function readConfigs(options: ReadOptions): Promise { +export async function readFrontendConfig(options: { + env: { [name: string]: string | undefined }; + appDistDir: string; + config: Config; + schema?: ConfigSchema; +}): Promise { const { env, appDistDir, config } = options; const appConfigs = readEnvConfig(env); diff --git a/plugins/app-backend/src/lib/config/types.ts b/plugins/app-backend/src/lib/config/types.ts new file mode 100644 index 0000000000..e6a0e9141b --- /dev/null +++ b/plugins/app-backend/src/lib/config/types.ts @@ -0,0 +1,27 @@ +/* + * 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 { LoggerService } from '@backstage/backend-plugin-api'; +import { AppConfig } from '@backstage/config'; + +export interface InjectOptions { + appConfigs: AppConfig[]; + /** Directory of the public web content */ + rootDir: string; + /** Directory of the static JS files to search for file to inject */ + staticDir: string; + logger: LoggerService; +} diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index b7ad65706c..cdb150e06c 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -25,7 +25,7 @@ import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib/config', () => ({ injectConfig: jest.fn(), - readConfigs: jest.fn(), + readFrontendConfig: jest.fn(), })); global.__non_webpack_require__ = { @@ -120,11 +120,13 @@ describe('createRouter with static fallback handler', () => { describe('createRouter config schema test', () => { const libConfigs = require('../lib/config'); const libConfigsActual = jest.requireActual('../lib/config'); - const readConfigsMock: jest.Mock = libConfigs.readConfigs; + const readFrontendConfigMock: jest.Mock = libConfigs.readFrontendConfig; beforeEach(() => { jest.resetAllMocks(); - readConfigsMock.mockImplementation(libConfigsActual.readConfigs); + readFrontendConfigMock.mockImplementation( + libConfigsActual.readFrontendConfig, + ); }); it('uses an external schema', async () => { @@ -155,7 +157,7 @@ describe('createRouter config schema test', () => { }), }); - const results = readConfigsMock.mock.results; + const results = readFrontendConfigMock.mock.results; expect(results.length).toBe(1); const mockedResult = results[0]; @@ -177,7 +179,7 @@ describe('createRouter config schema test', () => { appPackageName: 'example-app', }); - const results = readConfigsMock.mock.results; + const results = readFrontendConfigMock.mock.results; expect(results.length).toBe(1); const mockedResult = results[0]; diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index b205f16203..cd1c0e4ee2 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -26,7 +26,6 @@ import express from 'express'; import Router from 'express-promise-router'; import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; -import { injectConfig, readConfigs } from '../lib/config'; import { createStaticAssetMiddleware, findStaticAssets, @@ -44,6 +43,7 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import { injectConfig, readFrontendConfig } from '../lib/config'; // express uses mime v1 while we only have types for mime v2 type Mime = { lookup(arg0: string): string }; @@ -147,7 +147,7 @@ export async function createRouter( const appConfigs = disableConfigInjection ? undefined - : await readConfigs({ + : await readFrontendConfig({ config, appDistDir, env: process.env, @@ -266,7 +266,8 @@ async function createEntryPointRouter({ const staticDir = resolvePath(rootDir, 'static'); const injectedConfigPath = - appConfigs && (await injectConfig({ appConfigs, logger, staticDir })); + appConfigs && + (await injectConfig({ appConfigs, logger, rootDir, staticDir })); const router = Router(); diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 53a3d3967e..a2cee27f3b 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-app-node +## 0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config-loader@1.9.1-next.0 + +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config-loader@1.9.0 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index 5cab91886f..d282f52d4a 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.25-next.0", + "version": "0.1.25-next.2", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 44ec74ddd1..6b265a1f55 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-app-visualizer +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 54a8adfbd7..4cd69b28d7 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.10-next.0", + "version": "0.1.10-next.2", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index c7523808e5..4c67d88c20 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-app +## 0.1.0-next.2 + +### Patch Changes + +- 57bf6ae: Fix issue with `AlertDisplay` and other components defined with `AppRootElementBlueprint` not being rendered when at the `SignInWrapper` +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + +## 0.1.0-next.1 + +### Patch Changes + +- 52f9c5a: Deprecated the `namespace` option for `createExtensionBlueprint` and `createExtension`, these are no longer required and will default to the `pluginId` instead. + + You can migrate some of your extensions that use `createExtensionOverrides` to using `createFrontendModule` instead and providing a `pluginId` there. + + ```ts + // Before + createExtensionOverrides({ + extensions: [ + createExtension({ + name: 'my-extension', + namespace: 'my-namespace', + kind: 'test', + ... + }) + ], + }); + + // After + createFrontendModule({ + pluginId: 'my-namespace', + extensions: [ + createExtension({ + name: 'my-extension', + kind: 'test', + ... + }) + ], + }); + ``` + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + ## 0.1.0-next.0 ### Minor Changes diff --git a/plugins/app/api-report.md b/plugins/app/api-report.md index ca76ecb91b..6f2e6e0463 100644 --- a/plugins/app/api-report.md +++ b/plugins/app/api-report.md @@ -29,30 +29,6 @@ const appPlugin: FrontendPlugin< {}, {}, { - [x: `component:app/${string}`]: ExtensionDefinition<{ - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - ref: ComponentRef; - impl: ComponentType; - }, - 'core.component.component', - {} - >; - inputs: { - [x: string]: ExtensionInput< - AnyExtensionDataRef, - { - optional: boolean; - singleton: boolean; - } - >; - }; - kind: 'component'; - namespace: string; - name: string; - }>; app: ExtensionDefinition<{ config: {}; configInput: {}; @@ -71,7 +47,7 @@ const appPlugin: FrontendPlugin< >; }; kind: undefined; - namespace: 'app'; + namespace: undefined; name: undefined; }>; 'api:app/app-language': ExtensionDefinition<{ @@ -112,7 +88,7 @@ const appPlugin: FrontendPlugin< >; }; kind: undefined; - namespace: 'app'; + namespace: undefined; name: 'layout'; }>; 'app/nav': ExtensionDefinition<{ @@ -155,7 +131,7 @@ const appPlugin: FrontendPlugin< >; }; kind: undefined; - namespace: 'app'; + namespace: undefined; name: 'nav'; }>; 'app/root': ExtensionDefinition<{ @@ -220,7 +196,7 @@ const appPlugin: FrontendPlugin< >; }; kind: undefined; - namespace: 'app'; + namespace: undefined; name: 'root'; }>; 'app/routes': ExtensionDefinition<{ @@ -249,7 +225,7 @@ const appPlugin: FrontendPlugin< >; }; kind: undefined; - namespace: 'app'; + namespace: undefined; name: 'routes'; }>; 'api:app/app-theme': ExtensionDefinition<{ @@ -275,7 +251,7 @@ const appPlugin: FrontendPlugin< }>; 'theme:app/light': ExtensionDefinition<{ kind: 'theme'; - namespace: 'app'; + namespace: undefined; name: 'light'; config: {}; configInput: {}; @@ -284,7 +260,7 @@ const appPlugin: FrontendPlugin< }>; 'theme:app/dark': ExtensionDefinition<{ kind: 'theme'; - namespace: 'app'; + namespace: undefined; name: 'dark'; config: {}; configInput: {}; @@ -393,7 +369,7 @@ const appPlugin: FrontendPlugin< }>; 'app-root-element:app/oauth-request-dialog': ExtensionDefinition<{ kind: 'app-root-element'; - namespace: 'app'; + namespace: undefined; name: 'oauth-request-dialog'; config: {}; configInput: {}; @@ -436,7 +412,7 @@ const appPlugin: FrontendPlugin< >; }; kind: 'app-root-element'; - namespace: 'app'; + namespace: undefined; name: 'alert-display'; }>; 'api:app/discovery': ExtensionDefinition<{ diff --git a/plugins/app/package.json b/plugins/app/package.json index b6289ca5a2..fe0504d93e 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.1.0-next.0", + "version": "0.1.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -54,7 +54,7 @@ "@backstage/dev-utils": "workspace:^", "@backstage/frontend-test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0", "react": "^16.13.1 || ^17.0.0 || ^18.0.0" diff --git a/plugins/app/src/extensions/App.tsx b/plugins/app/src/extensions/App.tsx index 5c6d95bfbc..7f4b3d5ee1 100644 --- a/plugins/app/src/extensions/App.tsx +++ b/plugins/app/src/extensions/App.tsx @@ -27,7 +27,6 @@ import { ApiProvider } from '../../../../packages/core-app-api/src'; import { AppThemeProvider } from '../../../../packages/core-app-api/src/app/AppThemeProvider'; export const App = createExtension({ - namespace: 'app', attachTo: { id: 'root', input: 'app' }, inputs: { root: createExtensionInput([coreExtensionData.reactElement], { diff --git a/plugins/app/src/extensions/AppLayout.tsx b/plugins/app/src/extensions/AppLayout.tsx index 9fadba8a93..3a0560a807 100644 --- a/plugins/app/src/extensions/AppLayout.tsx +++ b/plugins/app/src/extensions/AppLayout.tsx @@ -23,7 +23,6 @@ import { import { SidebarPage } from '@backstage/core-components'; export const AppLayout = createExtension({ - namespace: 'app', name: 'layout', attachTo: { id: 'app/root', input: 'children' }, inputs: { diff --git a/plugins/app/src/extensions/AppNav.tsx b/plugins/app/src/extensions/AppNav.tsx index 3659f474b2..3e4fcebf8c 100644 --- a/plugins/app/src/extensions/AppNav.tsx +++ b/plugins/app/src/extensions/AppNav.tsx @@ -82,7 +82,6 @@ const SidebarNavItem = ( }; export const AppNav = createExtension({ - namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, inputs: { diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index bd786765f9..b973178e3a 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -16,7 +16,6 @@ import React, { ComponentType, - Fragment, PropsWithChildren, ReactNode, useState, @@ -53,7 +52,6 @@ import { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/ import { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath'; export const AppRoot = createExtension({ - namespace: 'app', name: 'root', attachTo: { id: 'app', input: 'root' }, inputs: { @@ -96,15 +94,8 @@ export const AppRoot = createExtension({ }); } - let content: React.ReactNode = ( - <> - {inputs.elements.map(el => ( - - {el.get(coreExtensionData.reactElement)} - - ))} - {inputs.children.get(coreExtensionData.reactElement)} - + let content: React.ReactNode = inputs.children.get( + coreExtensionData.reactElement, ); for (const wrapper of inputs.wrappers) { @@ -121,6 +112,9 @@ export const AppRoot = createExtension({ RouterComponent={inputs.router?.get( RouterBlueprint.dataRefs.component, )} + extraElements={inputs.elements?.map(el => + el.get(coreExtensionData.reactElement), + )} > {content} , @@ -188,6 +182,7 @@ export interface AppRouterProps { children?: ReactNode; SignInPageComponent?: ComponentType; RouterComponent?: ComponentType>; + extraElements?: Array; } function DefaultRouter(props: PropsWithChildren<{}>) { @@ -211,6 +206,7 @@ export function AppRouter(props: AppRouterProps) { children, SignInPageComponent, RouterComponent = DefaultRouter, + extraElements = [], } = props; const configApi = useApi(configApiRef); @@ -261,6 +257,7 @@ export function AppRouter(props: AppRouterProps) { return ( + {...extraElements} , @@ -28,7 +27,6 @@ export const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({ export const alertDisplayAppRootElement = AppRootElementBlueprint.makeWithOverrides({ - namespace: 'app', name: 'alert-display', config: { schema: { diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index 1f053979f2..c8c7f51d86 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 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 diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index cbdf2da557..ae9d913808 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -45,7 +45,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-auth0-provider/.eslintrc.js b/plugins/auth-backend-module-auth0-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md new file mode 100644 index 0000000000..f0596c1607 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-auth-backend-module-auth0-provider + +## 0.1.0-next.0 + +### Minor Changes + +- d908d8c: New module for `@backstage/plugin-auth-backend` that adds a Auth0 auth provider. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.5.2-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 diff --git a/plugins/auth-backend-module-auth0-provider/README.md b/plugins/auth-backend-module-auth0-provider/README.md new file mode 100644 index 0000000000..c2a98b0335 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/README.md @@ -0,0 +1,8 @@ +# Auth Module: Auth0 Provider + +This module provides an Auth0 auth provider implementation for `@backstage/plugin-auth-backend`. + +## Links + +- [Repository](https://gitlab.com/backstage/backstage/tree/master/plugins/auth-backend-module-auth0-provider) +- [Backstage Project Homepage](https://backstage.io) diff --git a/plugins/auth-backend-module-auth0-provider/api-report.md b/plugins/auth-backend-module-auth0-provider/api-report.md new file mode 100644 index 0000000000..fb7aa9d0ba --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/api-report.md @@ -0,0 +1,25 @@ +## API Report File for "@backstage/plugin-auth-backend-module-auth0-provider" + +> 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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; +import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; +import { PassportProfile } from '@backstage/plugin-auth-node'; + +// @public (undocumented) +export const auth0Authenticator: OAuthAuthenticator< + { + helper: PassportOAuthAuthenticatorHelper; + audience: string | undefined; + connection: string | undefined; + connectionScope: string | undefined; + }, + PassportProfile +>; + +// @public (undocumented) +const authModuleAuth0Provider: BackendFeature; +export default authModuleAuth0Provider; +``` diff --git a/plugins/auth-backend-module-auth0-provider/catalog-info.yaml b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml new file mode 100644 index 0000000000..26b540ca8a --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-auth0-provider + title: '@backstage/plugin-auth-backend-module-auth0-provider' + description: The auth0-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-auth0-provider/config.d.ts b/plugins/auth-backend-module-auth0-provider/config.d.ts new file mode 100644 index 0000000000..a0ec7d4eac --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/config.d.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + auth0?: { + [authEnv: string]: { + clientId: string; + /** + * @visibility secret + */ + clientSecret: string; + domain: string; + callbackUrl?: string; + audience?: string; + connection?: string; + connectionScope?: string; + }; + }; + }; + }; +} diff --git a/plugins/auth-backend-module-auth0-provider/dev/index.ts b/plugins/auth-backend-module-auth0-provider/dev/index.ts new file mode 100644 index 0000000000..c22389cc57 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/dev/index.ts @@ -0,0 +1,26 @@ +/* + * 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 { createBackend } from '@backstage/backend-defaults'; +import authPlugin from '@backstage/plugin-auth-backend'; +import authModuleAuth0Provider from '../src'; + +const backend = createBackend(); + +backend.add(authPlugin); +backend.add(authModuleAuth0Provider); + +backend.start(); diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json new file mode 100644 index 0000000000..c1c0cb83c9 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/plugin-auth-backend-module-auth0-provider", + "version": "0.1.0-next.0", + "description": "The auth0-provider backend module for the auth plugin.", + "backstage": { + "role": "backend-plugin-module", + "pluginId": "auth", + "pluginPackage": "@backstage/plugin-auth-backend" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/auth-backend-module-auth0-provider" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "config.d.ts" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "express": "^4.17.1", + "passport-auth0": "^1.4.3", + "passport-oauth2": "^1.6.1" + }, + "devDependencies": { + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "@types/passport-auth0": "^1.0.5", + "@types/passport-oauth2": "^1.4.15", + "supertest": "^6.3.3" + }, + "configSchema": "config.d.ts" +} diff --git a/plugins/auth-backend-module-auth0-provider/src/authenticator.ts b/plugins/auth-backend-module-auth0-provider/src/authenticator.ts new file mode 100644 index 0000000000..a042f113f8 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/src/authenticator.ts @@ -0,0 +1,116 @@ +/* + * 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 express from 'express'; +import { + createOAuthAuthenticator, + PassportOAuthAuthenticatorHelper, + PassportOAuthDoneCallback, + PassportProfile, +} from '@backstage/plugin-auth-node'; +import { Auth0Strategy } from './strategy'; + +/** @public */ +export const auth0Authenticator = createOAuthAuthenticator({ + defaultProfileTransform: + PassportOAuthAuthenticatorHelper.defaultProfileTransform, + initialize({ callbackUrl, config }) { + const clientID = config.getString('clientId'); + const clientSecret = config.getString('clientSecret'); + const domain = config.getString('domain'); + const audience = config.getOptionalString('audience'); + const connection = config.getOptionalString('connection'); + const connectionScope = config.getOptionalString('connectionScope'); + const callbackURL = config.getOptionalString('callbackUrl') ?? callbackUrl; + // Due to passport-auth0 forcing options.state = true, + // passport-oauth2 requires express-session to be installed + // so that the 'state' parameter of the oauth2 flow can be stored. + // This implementation of StateStore matches the NullStore found within + // passport-oauth2, which is the StateStore implementation used when options.state = false, + // allowing us to avoid using express-session in order to integrate with auth0. + const store = { + store(_req: express.Request, cb: any) { + cb(null, null); + }, + verify(_req: express.Request, _state: string, cb: any) { + cb(null, true); + }, + }; + + const helper = PassportOAuthAuthenticatorHelper.from( + new Auth0Strategy( + { + clientID, + clientSecret, + callbackURL, + domain, + store, + // We need passReqToCallback set to false to get params, but there's + // no matching type signature for that, so instead behold this beauty + passReqToCallback: false as true, + }, + ( + accessToken: string, + refreshToken: string, + params: any, + fullProfile: PassportProfile, + done: PassportOAuthDoneCallback, + ) => { + done( + undefined, + { + fullProfile, + accessToken, + params, + }, + { + refreshToken, + }, + ); + }, + ), + ); + return { helper, audience, connection, connectionScope }; + }, + + async start( + input, + { helper, audience, connection, connectionScope: connection_scope }, + ) { + return helper.start(input, { + accessType: 'offline', + prompt: 'consent', + ...(audience ? { audience } : {}), + ...(connection ? { connection } : {}), + ...(connection_scope ? { connection_scope } : {}), + }); + }, + + async authenticate( + input, + { helper, audience, connection, connectionScope: connection_scope }, + ) { + return helper.authenticate(input, { + ...(audience ? { audience } : {}), + ...(connection ? { connection } : {}), + ...(connection_scope ? { connection_scope } : {}), + }); + }, + + async refresh(input, { helper }) { + return helper.refresh(input); + }, +}); diff --git a/plugins/auth-backend-module-auth0-provider/src/index.ts b/plugins/auth-backend-module-auth0-provider/src/index.ts new file mode 100644 index 0000000000..eb8f4efcbe --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/src/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The auth0-provider backend module for the auth plugin. + * + * @packageDocumentation + */ + +export { auth0Authenticator } from './authenticator'; +export { authModuleAuth0Provider as default } from './module'; diff --git a/plugins/auth-backend-module-auth0-provider/src/module.test.ts b/plugins/auth-backend-module-auth0-provider/src/module.test.ts new file mode 100644 index 0000000000..630f1dd070 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/src/module.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import authPlugin from '@backstage/plugin-auth-backend'; +import { authModuleAuth0Provider } from './module'; +import request from 'supertest'; +import { decodeOAuthState } from '@backstage/plugin-auth-node'; + +describe('authModuleAuth0Provider', () => { + it('should start', async () => { + const { server } = await startTestBackend({ + features: [ + authPlugin, + authModuleAuth0Provider, + mockServices.rootConfig.factory({ + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + auth: { + providers: { + auth0: { + development: { + clientId: 'clientId', + clientSecret: 'clientSecret', + domain: 'domain', + connection: 'connection', + connectionScope: 'connectionScope', + }, + }, + }, + session: { + secret: 'secret', + }, + }, + }, + }), + ], + }); + + const agent = request.agent(server); + + const res = await agent.get('/api/auth/auth0/start?env=development'); + + expect(res.status).toEqual(302); + + const nonceCookie = agent.jar.getCookie('auth0-nonce', { + domain: 'localhost', + path: '/api/auth/auth0/handler', + script: false, + secure: false, + }); + expect(nonceCookie).toBeDefined(); + + const startUrl = new URL(res.get('location')); + expect(startUrl.origin).toBe('https://domain'); + expect(startUrl.pathname).toBe('/authorize'); + expect(Object.fromEntries(startUrl.searchParams)).toEqual({ + response_type: 'code', + scope: '', + client_id: 'clientId', + redirect_uri: `http://localhost:${server.port()}/api/auth/auth0/handler/frame`, + prompt: 'consent', + accessType: 'offline', + connection: 'connection', + connection_scope: 'connectionScope', + nonce: expect.any(String), + state: expect.any(String), + }); + + expect(decodeOAuthState(startUrl.searchParams.get('state')!)).toEqual({ + env: 'development', + nonce: decodeURIComponent(nonceCookie.value), + }); + }); +}); diff --git a/plugins/auth-backend-module-auth0-provider/src/module.ts b/plugins/auth-backend-module-auth0-provider/src/module.ts new file mode 100644 index 0000000000..fbdb752780 --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/src/module.ts @@ -0,0 +1,47 @@ +/* + * 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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { auth0Authenticator } from './authenticator'; + +/** @public */ +export const authModuleAuth0Provider = createBackendModule({ + pluginId: 'auth', + moduleId: 'auth0-provider', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, + }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'auth0', + factory: createOAuthProviderFactory({ + authenticator: auth0Authenticator, + signInResolverFactories: { + ...commonSignInResolvers, + }, + }), + }); + }, + }); + }, +}); diff --git a/plugins/auth-backend-module-auth0-provider/src/strategy.ts b/plugins/auth-backend-module-auth0-provider/src/strategy.ts new file mode 100644 index 0000000000..039885dddb --- /dev/null +++ b/plugins/auth-backend-module-auth0-provider/src/strategy.ts @@ -0,0 +1,45 @@ +/* + * 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 Auth0InternalStrategy from 'passport-auth0'; +import type { StateStore } from 'passport-oauth2'; + +/** @public */ +export interface Auth0StrategyOptionsWithRequest { + clientID: string; + clientSecret: string; + callbackURL: string; + domain: string; + passReqToCallback: true; + store: StateStore; +} + +/** @public */ +export class Auth0Strategy extends Auth0InternalStrategy { + constructor( + options: Auth0StrategyOptionsWithRequest, + verify: Auth0InternalStrategy.VerifyFunction, + ) { + const optionsWithURLs = { + ...options, + authorizationURL: `https://${options.domain}/authorize`, + tokenURL: `https://${options.domain}/oauth/token`, + userInfoURL: `https://${options.domain}/userinfo`, + apiUrl: `https://${options.domain}/api`, + }; + super(optionsWithURLs, verify); + } +} diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index fc3193783b..808fbd1ed6 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 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 diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index d4df833840..3828d23fac 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts index 61c80c4b3c..73f859df52 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.test.ts @@ -146,6 +146,34 @@ describe('AwsAlbProvider', () => { ); }); + it('Email is missing', async () => { + const jwt = await new SignJWT({ ...mockClaims, email: undefined }) + .setProtectedHeader({ alg: 'HS256', signer: 'SIGNER_ARN' }) + .sign(signingKey); + const req = { + header: jest.fn(name => { + if (name === ALB_JWT_HEADER) { + return jwt; + } else if (name === ALB_ACCESS_TOKEN_HEADER) { + return mockAccessToken; + } + return undefined; + }), + } as unknown as express.Request; + await expect( + awsAlbAuthenticator.authenticate( + { req }, + { + issuer: 'ISSUER_URL', + signer: undefined, + getKey: jest.fn().mockResolvedValue(signingKey), + }, + ), + ).rejects.toThrow( + 'Exception occurred during JWT processing: AuthenticationError: Missing email in the JWT token', + ); + }); + it('issuer is missing', async () => { const jwt = await new SignJWT({}) .setProtectedHeader({ alg: 'HS256' }) diff --git a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts index 8a32b05a92..2e7fe1cd26 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/authenticator.ts @@ -15,11 +15,11 @@ */ import { AuthenticationError } from '@backstage/errors'; -import { AwsAlbClaims, AwsAlbResult, AwsAlbProtectedHeader } from './types'; +import { AwsAlbClaims, AwsAlbProtectedHeader, AwsAlbResult } from './types'; import { jwtVerify } from 'jose'; import { - PassportProfile, createProxyAuthenticator, + PassportProfile, } from '@backstage/plugin-auth-node'; import NodeCache from 'node-cache'; import { makeProfileInfo, provisionKeyCache } from './helpers'; @@ -69,6 +69,10 @@ export const awsAlbAuthenticator = createProxyAuthenticator({ throw new AuthenticationError('Signer mismatch on JWT token'); } + if (!claims.email) { + throw new AuthenticationError(`Missing email in the JWT token`); + } + const fullProfile: PassportProfile = { provider: 'unknown', id: claims.sub, diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index f9f72069bb..fe0a12b069 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 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 diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index 920efa816f..8bf21e1924 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index 16f7b29526..0467b526db 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 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 diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 17869169d1..9a5183619c 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -45,7 +45,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-bitbucket-server-provider/.eslintrc.js b/plugins/auth-backend-module-bitbucket-server-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md new file mode 100644 index 0000000000..1fa316413e --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-auth-backend-module-bitbucket-server-provider + +## 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 diff --git a/plugins/auth-backend-module-bitbucket-server-provider/README.md b/plugins/auth-backend-module-bitbucket-server-provider/README.md new file mode 100644 index 0000000000..d3498ffc1b --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/README.md @@ -0,0 +1,8 @@ +# Auth Module: Bitbucket Server Provider + +This module provides an Bitbucket Server auth provider implementation for `@backstage/plugin-auth-backend`. + +## Links + +- [Repository](https://gitlab.com/backstage/backstage/tree/master/plugins/auth-backend-module-bitbucket-server-provider) +- [Backstage Project Homepage](https://backstage.io) diff --git a/plugins/auth-backend-module-bitbucket-server-provider/api-report.md b/plugins/auth-backend-module-bitbucket-server-provider/api-report.md new file mode 100644 index 0000000000..d5c484f6e3 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/api-report.md @@ -0,0 +1,33 @@ +## API Report File for "@backstage/plugin-auth-backend-module-bitbucket-server-provider" + +> 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 { OAuthAuthenticator } from '@backstage/plugin-auth-node'; +import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; +import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; +import { PassportProfile } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; + +// @public (undocumented) +const authModuleBitbucketServerProvider: BackendFeature; +export default authModuleBitbucketServerProvider; + +// @public (undocumented) +export const bitbucketServerAuthenticator: OAuthAuthenticator< + { + helper: PassportOAuthAuthenticatorHelper; + host: string; + }, + PassportProfile +>; + +// @public +export namespace bitbucketServerSignInResolvers { + const emailMatchingUserEntityProfileEmail: SignInResolverFactory< + OAuthAuthenticatorResult, + unknown + >; +} +``` diff --git a/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml new file mode 100644 index 0000000000..bcb659b1c2 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-bitbucket-server-provider + title: '@backstage/plugin-auth-backend-module-bitbucket-server-provider' + description: The bitbucket-server-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-bitbucket-server-provider/config.d.ts b/plugins/auth-backend-module-bitbucket-server-provider/config.d.ts new file mode 100644 index 0000000000..ee924f47f0 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/config.d.ts @@ -0,0 +1,34 @@ +/* + * 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 interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + bitbucketServer?: { + [authEnv: string]: { + clientId: string; + /** + * @visibility secret + */ + clientSecret: string; + host: string; + callbackUrl?: string; + }; + }; + }; + }; +} diff --git a/plugins/auth-backend-module-bitbucket-server-provider/dev/index.ts b/plugins/auth-backend-module-bitbucket-server-provider/dev/index.ts new file mode 100644 index 0000000000..b52bbf830b --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/dev/index.ts @@ -0,0 +1,26 @@ +/* + * 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 { createBackend } from '@backstage/backend-defaults'; +import authPlugin from '@backstage/plugin-auth-backend'; +import authModuleBitbucketServerProvider from '../src'; + +const backend = createBackend(); + +backend.add(authPlugin); +backend.add(authModuleBitbucketServerProvider); + +backend.start(); diff --git a/plugins/auth-backend-module-bitbucket-server-provider/package.json b/plugins/auth-backend-module-bitbucket-server-provider/package.json new file mode 100644 index 0000000000..f7ef903aab --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/package.json @@ -0,0 +1,52 @@ +{ + "name": "@backstage/plugin-auth-backend-module-bitbucket-server-provider", + "version": "0.1.0-next.0", + "description": "The bitbucket-server-provider backend module for the auth plugin.", + "backstage": { + "role": "backend-plugin-module", + "pluginId": "auth", + "pluginPackage": "@backstage/plugin-auth-backend" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/auth-backend-module-bitbucket-server-provider" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "config.d.ts" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "node-fetch": "^2.7.0", + "passport": "^0.7.0", + "passport-oauth2": "^1.6.1" + }, + "devDependencies": { + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "@types/passport-oauth2": "^1.4.15", + "supertest": "^6.3.3" + }, + "configSchema": "config.d.ts" +} diff --git a/plugins/auth-backend-module-bitbucket-server-provider/src/authenticator.ts b/plugins/auth-backend-module-bitbucket-server-provider/src/authenticator.ts new file mode 100644 index 0000000000..052100cd40 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/src/authenticator.ts @@ -0,0 +1,93 @@ +/* + * 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 { Strategy as OAuth2Strategy, VerifyCallback } from 'passport-oauth2'; +import { + createOAuthAuthenticator, + PassportOAuthAuthenticatorHelper, + PassportProfile, +} from '@backstage/plugin-auth-node'; +import { fetchProfile } from './helpers'; + +/** @public */ +export const bitbucketServerAuthenticator = createOAuthAuthenticator({ + defaultProfileTransform: + PassportOAuthAuthenticatorHelper.defaultProfileTransform, + initialize({ callbackUrl, config }) { + const clientID = config.getString('clientId'); + const clientSecret = config.getString('clientSecret'); + const host = config.getString('host'); + const callbackURL = config.getOptionalString('callbackUrl') ?? callbackUrl; + + const helper = PassportOAuthAuthenticatorHelper.from( + new OAuth2Strategy( + { + clientID, + clientSecret, + callbackURL, + authorizationURL: `https://${host}/rest/oauth2/latest/authorize`, + tokenURL: `https://${host}/rest/oauth2/latest/token`, + }, + ( + accessToken: string, + refreshToken: string, + params: any, + fullProfile: PassportProfile, + done: VerifyCallback, + ) => { + done( + undefined, + { fullProfile, params, accessToken }, + { refreshToken }, + ); + }, + ), + ); + + return { helper, host }; + }, + + async start(input, { helper }) { + return helper.start(input, { + accessType: 'offline', + prompt: 'consent', + }); + }, + + async authenticate(input, { helper, host }) { + const result = await helper.authenticate(input); + + // The OAuth2 strategy does not return a user profile, so we fetch it manually + const fullProfile = await fetchProfile({ + host, + accessToken: result.session.accessToken, + }); + + return { ...result, fullProfile }; + }, + + async refresh(input, { helper, host }) { + const result = await helper.refresh(input); + + // The OAuth2 strategy does not return a user profile, so we fetch it manually + const fullProfile = await fetchProfile({ + host, + accessToken: result.session.accessToken, + }); + + return { ...result, fullProfile }; + }, +}); diff --git a/plugins/auth-backend-module-bitbucket-server-provider/src/helpers.ts b/plugins/auth-backend-module-bitbucket-server-provider/src/helpers.ts new file mode 100644 index 0000000000..fc16253b27 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/src/helpers.ts @@ -0,0 +1,83 @@ +/* + * 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 fetch from 'node-fetch'; +import { PassportProfile } from '@backstage/plugin-auth-node'; + +export async function fetchProfile(options: { + host: string; + accessToken: string; +}): Promise { + const { host, accessToken } = options; + // Get current user name + let whoAmIResponse; + try { + whoAmIResponse = await fetch( + `https://${host}/plugins/servlet/applinks/whoami`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + } catch (e) { + throw new Error(`Failed to retrieve the username of the logged in user`); + } + + // A response.ok check here would be worthless as the Bitbucket API always returns 200 OK for this call + const username = whoAmIResponse.headers.get('X-Ausername'); + if (!username) { + throw new Error(`Failed to retrieve the username of the logged in user`); + } + + let userResponse; + try { + userResponse = await fetch( + `https://${host}/rest/api/latest/users/${username}?avatarSize=256`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); + } catch (e) { + throw new Error(`Failed to retrieve the user '${username}'`); + } + + if (!userResponse.ok) { + throw new Error(`Failed to retrieve the user '${username}'`); + } + + const user = await userResponse.json(); + + const passportProfile = { + provider: 'bitbucketServer', + id: user.id.toString(), + displayName: user.displayName, + username: user.name, + emails: [ + { + value: user.emailAddress, + }, + ], + } as PassportProfile; + + if (user.avatarUrl) { + passportProfile.photos = [{ value: `https://${host}${user.avatarUrl}` }]; + } + + return passportProfile; +} diff --git a/plugins/auth-backend-module-bitbucket-server-provider/src/index.ts b/plugins/auth-backend-module-bitbucket-server-provider/src/index.ts new file mode 100644 index 0000000000..0d32abfe22 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/src/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The bitbucket-server-provider backend module for the auth plugin. + * + * @packageDocumentation + */ + +export { bitbucketServerAuthenticator } from './authenticator'; +export { authModuleBitbucketServerProvider as default } from './module'; +export { bitbucketServerSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-bitbucket-server-provider/src/module.test.ts b/plugins/auth-backend-module-bitbucket-server-provider/src/module.test.ts new file mode 100644 index 0000000000..ec75df92f8 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/src/module.test.ts @@ -0,0 +1,81 @@ +/* + * 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 authPlugin from '@backstage/plugin-auth-backend'; +import { decodeOAuthState } from '@backstage/plugin-auth-node'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { authModuleBitbucketServerProvider } from './module'; +import request from 'supertest'; + +describe('authModuleBitbucketServerProvider', () => { + it('should start', async () => { + const { server } = await startTestBackend({ + features: [ + authPlugin, + authModuleBitbucketServerProvider, + mockServices.rootConfig.factory({ + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + auth: { + providers: { + bitbucketServer: { + development: { + clientId: 'cliendId', + clientSecret: 'clientSecret', + host: 'bitbucket.org', + }, + }, + }, + }, + }, + }), + ], + }); + + const agent = request.agent(server); + + const res = await agent.get( + '/api/auth/bitbucketServer/start?env=development', + ); + + expect(res.status).toEqual(302); + + const nonceCookie = agent.jar.getCookie('bitbucketServer-nonce', { + domain: 'localhost', + path: '/api/auth/bitbucketServer/handler', + script: false, + secure: false, + }); + expect(nonceCookie).toBeDefined(); + + const startUrl = new URL(res.get('location')); + expect(startUrl.origin).toBe('https://bitbucket.org'); + expect(startUrl.pathname).toBe('/rest/oauth2/latest/authorize'); + expect(Object.fromEntries(startUrl.searchParams)).toEqual({ + response_type: 'code', + client_id: 'cliendId', + redirect_uri: `http://localhost:${server.port()}/api/auth/bitbucketServer/handler/frame`, + state: expect.any(String), + }); + + expect(decodeOAuthState(startUrl.searchParams.get('state')!)).toEqual({ + env: 'development', + nonce: decodeURIComponent(nonceCookie.value), + }); + }); +}); diff --git a/plugins/auth-backend-module-bitbucket-server-provider/src/module.ts b/plugins/auth-backend-module-bitbucket-server-provider/src/module.ts new file mode 100644 index 0000000000..c49def87cd --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/src/module.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { bitbucketServerAuthenticator } from './authenticator'; +import { bitbucketServerSignInResolvers } from './resolvers'; + +/** @public */ +export const authModuleBitbucketServerProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'bitbucket-server-provider', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, + }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'bitbucketServer', + factory: createOAuthProviderFactory({ + authenticator: bitbucketServerAuthenticator, + signInResolverFactories: { + ...bitbucketServerSignInResolvers, + ...commonSignInResolvers, + }, + }), + }); + }, + }); + }, +}); diff --git a/plugins/auth-backend-module-bitbucket-server-provider/src/resolvers.ts b/plugins/auth-backend-module-bitbucket-server-provider/src/resolvers.ts new file mode 100644 index 0000000000..dc2ceef7e9 --- /dev/null +++ b/plugins/auth-backend-module-bitbucket-server-provider/src/resolvers.ts @@ -0,0 +1,55 @@ +/* + * 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 { + createSignInResolverFactory, + OAuthAuthenticatorResult, + PassportProfile, + SignInInfo, +} from '@backstage/plugin-auth-node'; + +/** + * Available sign-in resolvers for the Bitbucket Server auth provider. + * + * @public + */ +export namespace bitbucketServerSignInResolvers { + /** + * Looks up the user by matching their email to the entity email. + */ + export const emailMatchingUserEntityProfileEmail = + createSignInResolverFactory({ + create() { + return async ( + info: SignInInfo>, + ctx, + ) => { + const { profile } = info; + + if (!profile.email) { + throw new Error( + 'Login failed, user profile does not contain an email', + ); + } + + return ctx.signInWithCatalogUser({ + filter: { + 'spec.profile.email': profile.email, + }, + }); + }; + }, + }); +} diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index 28f18baf79..2fede81a00 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 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 diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index 7f5c8b97f4..06facf7803 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index ea27e5f2dc..deb3b76a88 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 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 diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 9ae551c9ce..eac8352dd4 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index cab470870e..5f07458cf3 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-github-provider +## 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 diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 75b6b39f0a..52f8038c01 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -43,7 +43,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index cc50ac6618..2bea6604bf 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 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 diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 6fa019c9d2..230ee2f1c6 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -45,7 +45,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 3bfd7f2feb..b041bb8f73 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-google-provider +## 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 diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index f81459cf1f..9c2c653119 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "A Google auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", @@ -48,7 +48,7 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "@types/passport-google-oauth20": "^2.0.3", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 9c01cc51b4..95225668b9 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 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/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/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/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + ## 0.2.0-next.0 ### Minor Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 70ee3926da..23a2698bbb 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index 0a50d8c7a6..ab1ded4e35 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 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 diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index bd956fca14..55018f0c3e 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -49,7 +49,7 @@ "@backstage/plugin-auth-backend": "workspace:^", "@types/passport-microsoft": "^1.0.0", "msw": "^1.0.0", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 590aa37742..7bf4ac1d1b 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 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 diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index ce8cab68c3..bfb5f09386 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -44,7 +44,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index 47ad278b68..bbcfdf03f3 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 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 diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index 63f13f5844..130eda97b4 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The oauth2-proxy-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/authenticator.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/authenticator.ts index 21fe52e824..c4542639d1 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/authenticator.ts @@ -42,6 +42,15 @@ export const oauth2ProxyAuthenticator = createProxyAuthenticator({ async initialize() {}, async authenticate({ req }) { try { + // This unpacking of the JWT is just a utility provided by the + // authenticator to make the fields available to the profile transform and + // sign-in resolvers. The JWT is already validated by the upstream OAuth2 + // Proxy, and since OAuth2 Proxy doesn't provide a way to authenticate + // forwarded requests, we don't do any additional validation here but + // instead trust that there is no way for attackers to bypass the OAuth2 + // Proxy. We could validate these individual ID tokens for some of the + // upstream providers, but that is currently not in scope for this + // authenticator. const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER); const jwt = authHeader?.match(/^Bearer[ ]+(\S+)$/i)?.[1]; const decodedJWT = jwt && decodeJwt(jwt); diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 51ac80e309..644b063880 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 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 diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index bf7091547b..fdf9bb1c35 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -52,7 +52,7 @@ "express-session": "^1.17.3", "jose": "^5.0.0", "msw": "^1.3.1", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index c588e5b98d..36f331d5ac 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 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 diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 1659bc3824..580190cd38 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", - "version": "0.1.0-next.0", + "version": "0.1.0-next.2", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -45,7 +45,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md index 8ac77804b1..217f5ada01 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 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 diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index bd3e332633..76a5148362 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -45,7 +45,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "supertest": "^6.3.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 5fb39c0962..5609d91489 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 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 + - @backstage/config@1.2.0 + +## 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/config@1.2.0 + ## 0.2.0-next.0 ### Minor Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 88612c827b..fc52b7c04f 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The pinniped-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -50,6 +50,6 @@ "jose": "^5.0.0", "msw": "^1.3.0", "passport": "^0.7.0", - "supertest": "^6.3.3" + "supertest": "^7.0.0" } } diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 3083f74262..ea14a31e36 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 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 + - @backstage/catalog-model@1.6.0 + +## 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/catalog-model@1.6.0 + ## 0.3.0-next.0 ### Minor Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index 62330f1bed..2207406864 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", @@ -48,6 +48,6 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "msw": "^2.0.8", - "supertest": "^6.3.3" + "supertest": "^7.0.0" } } diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 2e28c0c410..50b97cec2a 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-auth-backend +## 0.23.0-next.2 + +### Patch Changes + +- 527d973: Migrated the `Bitbucket Server` auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-bitbucket-server-provider` module. +- d908d8c: Migrated the `Auth0` auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-auth0-provider` module. +- 92118cd: Updated dependency `@node-saml/passport-saml` to `^5.0.0`. +- 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/catalog-client@1.7.0-next.1 + - @backstage/plugin-auth-backend-module-auth0-provider@0.1.0-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.2 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.1.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.2 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.2 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.23.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.2.0-next.1 + - @backstage/plugin-auth-node@0.5.2-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 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.0-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.3.0-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.1.0-next.1 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.2.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + ## 0.23.0-next.0 ### Minor Changes diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 51f80024ae..20690528d1 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -108,7 +108,7 @@ export type BitbucketPassportProfile = Profile & { }; }; -// @public (undocumented) +// @public @deprecated (undocumented) export type BitbucketServerOAuthResult = { fullProfile: Profile; params: { diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 177478fdb9..d7328a4005 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.23.0-next.0", + "version": "0.23.0-next.2", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin", @@ -50,9 +50,11 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-backend-module-atlassian-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-auth0-provider": "workspace:^", "@backstage/plugin-auth-backend-module-aws-alb-provider": "workspace:^", "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "workspace:^", "@backstage/plugin-auth-backend-module-bitbucket-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-bitbucket-server-provider": "workspace:^", "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "workspace:^", "@backstage/plugin-auth-backend-module-gcp-iap-provider": "workspace:^", "@backstage/plugin-auth-backend-module-github-provider": "workspace:^", @@ -68,7 +70,7 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "@google-cloud/firestore": "^7.0.0", - "@node-saml/passport-saml": "^4.0.4", + "@node-saml/passport-saml": "^5.0.0", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -115,7 +117,7 @@ "@types/passport-strategy": "^0.2.35", "@types/xml2js": "^0.4.7", "msw": "^1.0.0", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index c399eb08c4..44e7c5bb6e 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -14,40 +14,25 @@ * limitations under the License. */ -import express from 'express'; -import passport from 'passport'; -import Auth0Strategy from './strategy'; -import { - OAuthAdapter, - OAuthProviderOptions, - OAuthHandlers, - OAuthResponse, - OAuthEnvironmentHandler, - OAuthStartRequest, - encodeState, - OAuthRefreshRequest, - OAuthResult, -} from '../../lib/oauth'; -import { - executeFetchUserProfileStrategy, - executeFrameHandlerStrategy, - executeRedirectStrategy, - executeRefreshTokenStrategy, - makeProfileInfo, - PassportDoneCallback, -} from '../../lib/passport'; -import { OAuthStartResponse, AuthHandler } from '../types'; +import { OAuthProviderOptions, OAuthResult } from '../../lib/oauth'; + +import { AuthHandler } from '../types'; import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { StateStore } from 'passport-oauth2'; import { AuthResolverContext, + createOAuthProviderFactory, SignInResolver, } from '@backstage/plugin-auth-node'; +import { + adaptLegacyOAuthHandler, + adaptLegacyOAuthSignInResolver, +} from '../../lib/legacy'; +import { auth0Authenticator } from '@backstage/plugin-auth-backend-module-auth0-provider'; -type PrivateInfo = { - refreshToken: string; -}; - +/** + * @public + * @deprecated The Auth0 auth provider was extracted to `@backstage/plugin-auth-backend-module-auth0-provider`. + */ export type Auth0AuthProviderOptions = OAuthProviderOptions & { domain: string; signInResolver?: SignInResolver; @@ -58,155 +43,6 @@ export type Auth0AuthProviderOptions = OAuthProviderOptions & { connectionScope?: string; }; -export class Auth0AuthProvider implements OAuthHandlers { - private readonly _strategy: Auth0Strategy; - private readonly signInResolver?: SignInResolver; - private readonly authHandler: AuthHandler; - private readonly resolverContext: AuthResolverContext; - private readonly audience?: string; - private readonly connection?: string; - private readonly connectionScope?: string; - - /** - * Due to passport-auth0 forcing options.state = true, - * passport-oauth2 requires express-session to be installed - * so that the 'state' parameter of the oauth2 flow can be stored. - * This implementation of StateStore matches the NullStore found within - * passport-oauth2, which is the StateStore implementation used when options.state = false, - * allowing us to avoid using express-session in order to integrate with auth0. - */ - private store: StateStore = { - store(_req: express.Request, cb: any) { - cb(null, null); - }, - verify(_req: express.Request, _state: string, cb: any) { - cb(null, true); - }, - }; - - constructor(options: Auth0AuthProviderOptions) { - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.resolverContext = options.resolverContext; - this.audience = options.audience; - this.connection = options.connection; - this.connectionScope = options.connectionScope; - this._strategy = new Auth0Strategy( - { - clientID: options.clientId, - clientSecret: options.clientSecret, - callbackURL: options.callbackUrl, - domain: options.domain, - // We need passReqToCallback set to false to get params, but there's - // no matching type signature for that, so instead behold this beauty - passReqToCallback: false as true, - store: this.store, - }, - ( - accessToken: any, - refreshToken: any, - params: any, - fullProfile: passport.Profile, - done: PassportDoneCallback, - ) => { - done( - undefined, - { - fullProfile, - accessToken, - refreshToken, - params, - }, - { - refreshToken, - }, - ); - }, - ); - } - - async start(req: OAuthStartRequest): Promise { - return await executeRedirectStrategy(req, this._strategy, { - accessType: 'offline', - prompt: 'consent', - scope: req.scope, - state: encodeState(req.state), - ...(this.audience ? { audience: this.audience } : {}), - ...(this.connection ? { connection: this.connection } : {}), - ...(this.connectionScope - ? { connection_scope: this.connectionScope } - : {}), - }); - } - - async handler(req: express.Request) { - const { result, privateInfo } = await executeFrameHandlerStrategy< - OAuthResult, - PrivateInfo - >(req, this._strategy, { - ...(this.audience ? { audience: this.audience } : {}), - ...(this.connection ? { connection: this.connection } : {}), - ...(this.connectionScope - ? { connection_scope: this.connectionScope } - : {}), - }); - - return { - response: await this.handleResult(result), - refreshToken: privateInfo.refreshToken, - }; - } - - async refresh(req: OAuthRefreshRequest) { - const { accessToken, refreshToken, params } = - await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); - - const fullProfile = await executeFetchUserProfileStrategy( - this._strategy, - accessToken, - ); - - return { - response: await this.handleResult({ - fullProfile, - params, - accessToken, - }), - refreshToken, - }; - } - - private async handleResult(result: OAuthResult) { - const { profile } = await this.authHandler(result, this.resolverContext); - - const response: OAuthResponse = { - providerInfo: { - idToken: result.params.id_token, - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds: result.params.expires_in, - }, - profile, - }; - - if (this.signInResolver) { - response.backstageIdentity = await this.signInResolver( - { - result, - profile, - }, - this.resolverContext, - ); - } - - return response; - } -} - /** * Auth provider integration for auth0 auth * @@ -230,44 +66,10 @@ export const auth0 = createAuthProviderIntegration({ resolver: SignInResolver; }; }) { - return ({ providerId, globalConfig, config, resolverContext }) => - OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const clientId = envConfig.getString('clientId'); - const clientSecret = envConfig.getString('clientSecret'); - const domain = envConfig.getString('domain'); - const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); - const audience = envConfig.getOptionalString('audience'); - const connection = envConfig.getOptionalString('connection'); - const connectionScope = envConfig.getOptionalString('connectionScope'); - const callbackUrl = - customCallbackUrl || - `${globalConfig.baseUrl}/${providerId}/handler/frame`; - - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ fullProfile, params }) => ({ - profile: makeProfileInfo(fullProfile, params.id_token), - }); - - const signInResolver = options?.signIn?.resolver; - - const provider = new Auth0AuthProvider({ - clientId, - clientSecret, - callbackUrl, - domain, - authHandler, - signInResolver, - resolverContext, - audience, - connection, - connectionScope, - }); - - return OAuthAdapter.fromConfig(globalConfig, provider, { - providerId, - callbackUrl, - }); - }); + return createOAuthProviderFactory({ + authenticator: auth0Authenticator, + profileTransform: adaptLegacyOAuthHandler(options?.authHandler), + signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), + }); }, }); diff --git a/plugins/auth-backend/src/providers/bitbucketServer/provider.test.ts b/plugins/auth-backend/src/providers/bitbucketServer/provider.test.ts deleted file mode 100644 index 3756e00b38..0000000000 --- a/plugins/auth-backend/src/providers/bitbucketServer/provider.test.ts +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as helpers from '../../lib/passport/PassportStrategyHelper'; -import { makeProfileInfo } from '../../lib/passport'; -import { - bitbucketServer, - BitbucketServerAuthProvider, - BitbucketServerOAuthResult, -} from './provider'; -import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '@backstage/backend-test-utils'; -import { rest } from 'msw'; -import { AuthResolverContext } from '@backstage/plugin-auth-node'; - -jest.mock('../../lib/passport/PassportStrategyHelper', () => { - return { - ...jest.requireActual('../../lib/passport/PassportStrategyHelper'), - executeFrameHandlerStrategy: jest.fn(), - executeRefreshTokenStrategy: jest.fn(), - executeFetchUserProfileStrategy: jest.fn(), - }; -}); - -const mockFrameHandler = jest.spyOn( - helpers, - 'executeFrameHandlerStrategy', -) as unknown as jest.MockedFunction< - () => Promise<{ - result: BitbucketServerOAuthResult; - privateInfo: { refreshToken?: string }; - }> ->; - -const passportProfile = { - id: '123', - username: 'john.doe', - provider: 'bitubcketServer', - displayName: 'John Doe', - emails: [{ value: 'john@doe.com' }], - photos: [{ value: 'https://bitbucket.org/user/123/avatar' }], -}; - -const mockHost = 'bitbucket.org'; -const mockBaseUrl = `https://${mockHost}`; - -const whoAmIHandler = (options?: { fail?: boolean; value?: string }) => - rest.get( - `${mockBaseUrl}/plugins/servlet/applinks/whoami`, - (_req, res, ctx) => { - if (options?.fail) { - res.networkError('error'); - } - return res( - ctx.status(200), - ctx.set('X-Ausername', options?.value ?? passportProfile.username), - ); - }, - ); - -const getUserHandler = (options?: { - fail?: boolean; - status?: number; - avatarUrl?: string; - noDisplayName?: boolean; - noUserName?: boolean; -}) => - rest.get( - `${mockBaseUrl}/rest/api/latest/users/${passportProfile.username}`, - (_req, res, ctx) => { - if (options?.fail) { - res.networkError('error'); - } - return res( - ctx.status(options?.status ?? 200), - ctx.json({ - name: options?.noUserName ? undefined : 'john.doe', - emailAddress: 'john@doe.com', - id: 123, - displayName: options?.noDisplayName ? undefined : 'John Doe', - active: true, - slug: 'john.doe', - type: 'NORMAL', - links: { - self: [ - { - href: 'https://bitbucket.org/users/john.doe', - }, - ], - }, - avatarUrl: options?.avatarUrl ?? '/user/123/avatar', - }), - ); - }, - ); - -describe('BitbucketServerAuthProvider', () => { - const provider = new BitbucketServerAuthProvider({ - resolverContext: { - signInWithCatalogUser: jest.fn(info => { - return { - token: `token-for-user:${info.filter['spec.profile.email']}`, - }; - }), - } as unknown as AuthResolverContext, - signInResolver: - bitbucketServer.resolvers.emailMatchingUserEntityProfileEmail(), - authHandler: async ({ fullProfile }) => ({ - profile: makeProfileInfo(fullProfile), - }), - callbackUrl: 'mock', - clientId: 'mock', - clientSecret: 'mock', - host: mockHost, - authorizationUrl: 'mock', - tokenUrl: 'mock', - }); - - describe('when transforming to type OAuthResponse', () => { - const server = setupServer(); - registerMswTestHooks(server); - - it('should map to a valid response', async () => { - server.use(whoAmIHandler(), getUserHandler()); - - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - - const expected = { - backstageIdentity: { - token: 'token-for-user:john@doe.com', - }, - providerInfo: { - accessToken: '19xasczxcm9n7gacn9jdgm19me', - scope: 'REPO_READ', - }, - profile: { - email: 'john@doe.com', - displayName: 'John Doe', - picture: 'https://bitbucket.org/user/123/avatar', - }, - }; - - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - const { response } = await provider.handler({} as any); - expect(response).toEqual(expected); - }); - - it('should throw if whoami fails', async () => { - server.use(whoAmIHandler({ fail: true }), getUserHandler()); - - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - - await expect(provider.handler({} as any)).rejects.toThrow( - `Failed to retrieve the username of the logged in user`, - ); - }); - - it('should throw if whoami returns an invalid response', async () => { - server.use(whoAmIHandler({ value: '' }), getUserHandler()); - - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - - await expect(provider.handler({} as any)).rejects.toThrow( - `Failed to retrieve the username of the logged in user`, - ); - }); - - it('should throw if get user fails', async () => { - server.use(whoAmIHandler(), getUserHandler({ fail: true })); - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - - await expect(provider.handler({} as any)).rejects.toThrow( - `Failed to retrieve the user '${passportProfile.username}'`, - ); - }); - - it('should throw if get user is not ok', async () => { - server.use(whoAmIHandler(), getUserHandler({ status: 500 })); - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - - await expect(provider.handler({} as any)).rejects.toThrow( - `Failed to retrieve the user '${passportProfile.username}'`, - ); - }); - - it('should not set an avatar url if not given', async () => { - server.use(whoAmIHandler(), getUserHandler({ avatarUrl: '' })); - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - - const expected = { - backstageIdentity: { - token: 'token-for-user:john@doe.com', - }, - providerInfo: { - accessToken: '19xasczxcm9n7gacn9jdgm19me', - scope: 'REPO_READ', - }, - profile: { - email: 'john@doe.com', - displayName: 'John Doe', - }, - }; - - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - const { response } = await provider.handler({} as any); - expect(response).toEqual(expected); - }); - - it('should fallback to the username if no displayName is given', async () => { - server.use(whoAmIHandler(), getUserHandler({ noDisplayName: true })); - - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - - const expected = { - backstageIdentity: { - token: 'token-for-user:john@doe.com', - }, - providerInfo: { - accessToken: '19xasczxcm9n7gacn9jdgm19me', - scope: 'REPO_READ', - }, - profile: { - email: 'john@doe.com', - displayName: 'john.doe', - picture: 'https://bitbucket.org/user/123/avatar', - }, - }; - - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - const { response } = await provider.handler({} as any); - expect(response).toEqual(expected); - }); - - it('should fallback to the user id if no name is given', async () => { - server.use( - whoAmIHandler(), - getUserHandler({ noDisplayName: true, noUserName: true }), - ); - - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - - const expected = { - backstageIdentity: { - token: 'token-for-user:john@doe.com', - }, - providerInfo: { - accessToken: '19xasczxcm9n7gacn9jdgm19me', - scope: 'REPO_READ', - }, - profile: { - email: 'john@doe.com', - displayName: '123', - picture: 'https://bitbucket.org/user/123/avatar', - }, - }; - - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: {}, - }); - const { response } = await provider.handler({} as any); - expect(response).toEqual(expected); - }); - }); - - describe('when authenticating', () => { - const server = setupServer(); - registerMswTestHooks(server); - - it('should forward the refresh token', async () => { - server.use(whoAmIHandler(), getUserHandler()); - - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: { refreshToken: 'refresh-token' }, - }); - - const response = await provider.handler({} as any); - - const expected = { - response: { - backstageIdentity: { - token: 'token-for-user:john@doe.com', - }, - providerInfo: { - accessToken: '19xasczxcm9n7gacn9jdgm19me', - scope: 'REPO_READ', - }, - profile: { - email: 'john@doe.com', - displayName: 'John Doe', - picture: 'https://bitbucket.org/user/123/avatar', - }, - }, - refreshToken: 'refresh-token', - }; - - expect(response).toEqual(expected); - }); - - it('should forward a new refresh token on refresh', async () => { - server.use(whoAmIHandler(), getUserHandler()); - - const accessToken = '19xasczxcm9n7gacn9jdgm19me'; - const params = { scope: 'REPO_READ' }; - const mockRefreshToken = jest.spyOn( - helpers, - 'executeRefreshTokenStrategy', - ) as unknown as jest.MockedFunction<() => Promise<{}>>; - mockRefreshToken.mockResolvedValueOnce({ - accessToken, - refreshToken: 'dont-forget-to-send-refresh', - params, - }); - mockFrameHandler.mockResolvedValueOnce({ - result: { fullProfile: passportProfile, accessToken, params }, - privateInfo: { refreshToken: 'refresh-token' }, - }); - - const expected = { - response: { - backstageIdentity: { - token: 'token-for-user:john@doe.com', - }, - providerInfo: { - accessToken: '19xasczxcm9n7gacn9jdgm19me', - scope: 'REPO_READ', - }, - profile: { - email: 'john@doe.com', - displayName: 'John Doe', - picture: 'https://bitbucket.org/user/123/avatar', - }, - }, - refreshToken: 'dont-forget-to-send-refresh', - }; - const response = await provider.refresh({ scope: 'REPO_WRITE' } as any); - - expect(response).toEqual(expected); - }); - }); -}); diff --git a/plugins/auth-backend/src/providers/bitbucketServer/provider.ts b/plugins/auth-backend/src/providers/bitbucketServer/provider.ts index d66f7f33c0..c49cf5b7b1 100644 --- a/plugins/auth-backend/src/providers/bitbucketServer/provider.ts +++ b/plugins/auth-backend/src/providers/bitbucketServer/provider.ts @@ -14,40 +14,28 @@ * limitations under the License. */ -import { - encodeState, - OAuthAdapter, - OAuthEnvironmentHandler, - OAuthHandlers, - OAuthProviderOptions, - OAuthRefreshRequest, - OAuthResponse, - OAuthStartRequest, -} from '../../lib/oauth'; -import { Strategy as OAuth2Strategy, VerifyCallback } from 'passport-oauth2'; -import { - executeFetchUserProfileStrategy, - executeFrameHandlerStrategy, - executeRedirectStrategy, - executeRefreshTokenStrategy, - makeProfileInfo, -} from '../../lib/passport'; -import { AuthHandler, OAuthStartResponse } from '../types'; -import express from 'express'; -import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; import { Profile as PassportProfile } from 'passport'; -import { commonByEmailResolver } from '../resolvers'; -import fetch from 'node-fetch'; import { AuthResolverContext, + createOAuthProviderFactory, SignInResolver, } from '@backstage/plugin-auth-node'; +import { + bitbucketServerAuthenticator, + bitbucketServerSignInResolvers, +} from '@backstage/plugin-auth-backend-module-bitbucket-server-provider'; +import { OAuthProviderOptions } from '../../lib/oauth'; +import { + adaptLegacyOAuthHandler, + adaptLegacyOAuthSignInResolver, +} from '../../lib/legacy'; +import { AuthHandler } from '../types'; +import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -type PrivateInfo = { - refreshToken: string; -}; - -/** @public */ +/** + * @public + * @deprecated The Bitbucket Server auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-server-provider`. + */ export type BitbucketServerOAuthResult = { fullProfile: PassportProfile; params: { @@ -60,6 +48,10 @@ export type BitbucketServerOAuthResult = { refreshToken?: string; }; +/** + * @public + * @deprecated The Bitbucket Server auth provider was extracted to `@backstage/plugin-auth-backend-module-bitbucket-server-provider`. + */ export type BitbucketServerAuthProviderOptions = OAuthProviderOptions & { host: string; authorizationUrl: string; @@ -69,176 +61,6 @@ export type BitbucketServerAuthProviderOptions = OAuthProviderOptions & { resolverContext: AuthResolverContext; }; -export class BitbucketServerAuthProvider implements OAuthHandlers { - private readonly signInResolver?: SignInResolver; - private readonly authHandler: AuthHandler; - private readonly resolverContext: AuthResolverContext; - private readonly strategy: OAuth2Strategy; - private readonly host: string; - - constructor(options: BitbucketServerAuthProviderOptions) { - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.resolverContext = options.resolverContext; - this.strategy = new OAuth2Strategy( - { - authorizationURL: options.authorizationUrl, - tokenURL: options.tokenUrl, - clientID: options.clientId, - clientSecret: options.clientSecret, - callbackURL: options.callbackUrl, - }, - ( - accessToken: string, - refreshToken: string, - params: any, - fullProfile: PassportProfile, - done: VerifyCallback, - ) => { - done(undefined, { fullProfile, params, accessToken }, { refreshToken }); - }, - ); - this.host = options.host; - } - - async start(req: OAuthStartRequest): Promise { - return await executeRedirectStrategy(req, this.strategy, { - accessType: 'offline', - prompt: 'consent', - scope: req.scope, - state: encodeState(req.state), - }); - } - - async handler( - req: express.Request, - ): Promise<{ response: OAuthResponse; refreshToken?: string }> { - const { result, privateInfo } = await executeFrameHandlerStrategy< - BitbucketServerOAuthResult, - PrivateInfo - >(req, this.strategy); - - return { - response: await this.handleResult(result), - refreshToken: privateInfo.refreshToken, - }; - } - - async refresh( - req: OAuthRefreshRequest, - ): Promise<{ response: OAuthResponse; refreshToken?: string }> { - const { accessToken, refreshToken, params } = - await executeRefreshTokenStrategy( - this.strategy, - req.refreshToken, - req.scope, - ); - const fullProfile = await executeFetchUserProfileStrategy( - this.strategy, - accessToken, - ); - return { - response: await this.handleResult({ - fullProfile, - params, - accessToken, - }), - refreshToken, - }; - } - - private async handleResult( - result: BitbucketServerOAuthResult, - ): Promise { - // The OAuth2 strategy does not return a user profile -> let's fetch it before calling the auth handler - result.fullProfile = await this.fetchProfile(result); - const { profile } = await this.authHandler(result, this.resolverContext); - - let backstageIdentity = undefined; - if (this.signInResolver) { - backstageIdentity = await this.signInResolver( - { result, profile }, - this.resolverContext, - ); - } - - return { - providerInfo: { - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds: result.params.expires_in, - }, - profile, - backstageIdentity, - }; - } - - private async fetchProfile( - result: BitbucketServerOAuthResult, - ): Promise { - // Get current user name - let whoAmIResponse; - try { - whoAmIResponse = await fetch( - `https://${this.host}/plugins/servlet/applinks/whoami`, - { - headers: { - Authorization: `Bearer ${result.accessToken}`, - }, - }, - ); - } catch (e) { - throw new Error(`Failed to retrieve the username of the logged in user`); - } - - // A response.ok check here would be worthless as the Bitbucket API always returns 200 OK for this call - const username = whoAmIResponse.headers.get('X-Ausername'); - if (!username) { - throw new Error(`Failed to retrieve the username of the logged in user`); - } - - let userResponse; - try { - userResponse = await fetch( - `https://${this.host}/rest/api/latest/users/${username}?avatarSize=256`, - { - headers: { - Authorization: `Bearer ${result.accessToken}`, - }, - }, - ); - } catch (e) { - throw new Error(`Failed to retrieve the user '${username}'`); - } - - if (!userResponse.ok) { - throw new Error(`Failed to retrieve the user '${username}'`); - } - - const user = await userResponse.json(); - - const passportProfile = { - provider: 'bitbucketServer', - id: user.id.toString(), - displayName: user.displayName, - username: user.name, - emails: [ - { - value: user.emailAddress, - }, - ], - } as PassportProfile; - - if (user.avatarUrl) { - passportProfile.photos = [ - { value: `https://${this.host}${user.avatarUrl}` }, - ]; - } - - return passportProfile; - } -} - export const bitbucketServer = createAuthProviderIntegration({ create(options?: { /** @@ -257,48 +79,38 @@ export const bitbucketServer = createAuthProviderIntegration({ resolver: SignInResolver; }; }) { - return ({ providerId, globalConfig, config, resolverContext }) => - OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const clientId = envConfig.getString('clientId'); - const clientSecret = envConfig.getString('clientSecret'); - const host = envConfig.getString('host'); - const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); - const callbackUrl = - customCallbackUrl || - `${globalConfig.baseUrl}/${providerId}/handler/frame`; - const authorizationUrl = `https://${host}/rest/oauth2/latest/authorize`; - const tokenUrl = `https://${host}/rest/oauth2/latest/token`; - - const authHandler: AuthHandler = - options?.authHandler - ? options.authHandler - : async ({ fullProfile }) => ({ - profile: makeProfileInfo(fullProfile), - }); - - const provider = new BitbucketServerAuthProvider({ - callbackUrl, - clientId, - clientSecret, - host, - authorizationUrl, - tokenUrl, - authHandler, - signInResolver: options?.signIn?.resolver, - resolverContext, - }); - - return OAuthAdapter.fromConfig(globalConfig, provider, { - providerId, - callbackUrl, - }); - }); + return createOAuthProviderFactory({ + authenticator: bitbucketServerAuthenticator, + profileTransform: adaptLegacyOAuthHandler(options?.authHandler), + signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), + }); }, resolvers: { /** * Looks up the user by matching their email to the entity email. */ emailMatchingUserEntityProfileEmail: - (): SignInResolver => commonByEmailResolver, + (): SignInResolver => { + const resolver = + bitbucketServerSignInResolvers.emailMatchingUserEntityProfileEmail(); + return async (info, ctx) => { + return resolver( + { + profile: info.profile, + result: { + fullProfile: info.result.fullProfile, + session: { + accessToken: info.result.accessToken, + tokenType: info.result.params.token_type ?? 'bearer', + scope: info.result.params.scope, + expiresInSeconds: info.result.params.expires_in, + refreshToken: info.result.refreshToken, + }, + }, + }, + ctx, + ); + }; + }, }, }); diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index d922034e2f..83166456c7 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -172,7 +172,7 @@ export const saml = createAuthProviderIntegration({ logoutUrl: config.getOptionalString('logoutUrl'), audience: config.getString('audience'), issuer: config.getString('issuer'), - cert: config.getString('cert'), + idpCert: config.getString('cert'), privateKey: config.getOptionalString('privateKey'), authnContext: config.getOptionalStringArray('authnContext'), identifierFormat: config.getOptionalString('identifierFormat'), diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index c3773c052e..c76062947d 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-auth-node +## 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 diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index a3750819f9..5a8750127d 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -503,6 +503,7 @@ export class PassportOAuthAuthenticatorHelper { // (undocumented) authenticate( input: OAuthAuthenticatorAuthenticateInput, + options?: Record, ): Promise>; // (undocumented) static defaultProfileTransform: ProfileTransform< diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 82ffbc4c7a..5a6ed6498e 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.5.2-next.0", + "version": "0.5.2-next.2", "backstage": { "role": "node-library", "pluginId": "auth", @@ -62,7 +62,7 @@ "express-promise-router": "^4.1.1", "lodash": "^4.17.21", "msw": "^1.0.0", - "supertest": "^6.1.3", + "supertest": "^7.0.0", "uuid": "^9.0.0" } } diff --git a/plugins/auth-node/src/oauth/PassportOAuthAuthenticatorHelper.ts b/plugins/auth-node/src/oauth/PassportOAuthAuthenticatorHelper.ts index c03a9cb56f..dfcdb3d297 100644 --- a/plugins/auth-node/src/oauth/PassportOAuthAuthenticatorHelper.ts +++ b/plugins/auth-node/src/oauth/PassportOAuthAuthenticatorHelper.ts @@ -85,12 +85,13 @@ export class PassportOAuthAuthenticatorHelper { async authenticate( input: OAuthAuthenticatorAuthenticateInput, + options?: Record, ): Promise> { const { result, privateInfo } = await PassportHelpers.executeFrameHandlerStrategy< PassportOAuthResult, PassportOAuthPrivateInfo - >(input.req, this.#strategy); + >(input.req, this.#strategy, options); return { fullProfile: result.fullProfile as PassportProfile, diff --git a/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts b/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts index 82b4918326..ed8f4baeeb 100644 --- a/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts +++ b/plugins/auth-node/src/sign-in/readDeclarativeSignInResolver.ts @@ -66,6 +66,8 @@ export function readDeclarativeSignInResolver( } } - throw new Error('Failed to sign-in, unable to resolve user identity'); + throw new Error( + 'Failed to sign-in, unable to resolve user identity. Please verify that your catalog contains the expected User entities that would match your configured sign-in resolver.', + ); }; } diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index f88d6210a8..36bb271073 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-auth-react +## 0.1.6-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/errors@1.2.4 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + ## 0.1.5 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index ae534e89d9..1c61b07e1c 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.5", + "version": "0.1.6-next.1", "description": "Web library for the auth plugin", "backstage": { "role": "web-library", @@ -49,7 +49,7 @@ "@backstage/cli": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 3de470cfba..a010420000 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.15.0-next.0 + ## 0.2.22 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index eeb5bd6ff6..c9d2472573 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.2.22", + "version": "0.2.23-next.0", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 5c09b5789a..5c519e7c84 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @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/integration@1.14.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.4.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 66afec1f99..92f11d4b63 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.4.2-next.0", + "version": "0.4.2-next.2", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 2f750f48e8..0b5e3492e2 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 8d27517cf5..aa0b3663c7 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.2.2-next.0", + "version": "0.2.2-next.2", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index 65d193523b..f1a2202ece 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/backend-openapi-utils@0.1.18-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @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/plugin-catalog-node@1.12.7-next.1 + ## 0.4.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 97517b0b43..3de01fe79e 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.4.0-next.0", + "version": "0.4.0-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 27ee19cb99..dc6259f380 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.3.2-next.2 + +### Patch Changes + +- 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/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.23-next.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.3.2-next.1 + +### Patch Changes + +- 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/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.3.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 59afec4970..4f0007bb44 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", - "version": "0.3.2-next.0", + "version": "0.3.2-next.2", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 1260480994..3abefd2471 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @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/integration@1.14.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 58719409ce..af12601f87 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.2.2-next.0", + "version": "0.2.2-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 606ead08be..6e8c52cacc 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.3.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index d48a43ad9a..13904c905b 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index d19ce42b01..6be0ec82b3 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @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-catalog-node@1.12.7-next.2 + +## 0.2.2-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-catalog-node@1.12.7-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index fa395efa22..caebb75b34 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.2.2-next.0", + "version": "0.2.2-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index db1b68c801..4b989e09cc 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.7.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.3.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index b738010b4a..66f0669cd6 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.3.0-next.0", + "version": "0.3.0-next.2", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 502bc5879e..995dbd1134 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-backend-module-github +## 0.7.3-next.2 + +### Patch Changes + +- 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/plugin-catalog-backend@1.26.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.7.3-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.7.3-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index dad81f8681..025d845644 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -5,6 +5,7 @@ ```ts import { AnalyzeOptions } from '@backstage/plugin-catalog-node'; import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogApi } from '@backstage/catalog-client'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; @@ -129,6 +130,7 @@ export type GithubLocationAnalyzerOptions = { tokenManager?: TokenManager; auth?: AuthService; githubCredentialsProvider?: GithubCredentialsProvider; + catalog?: CatalogApi; }; // @public diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index adc448cf27..8b05d0f896 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.7.3-next.0", + "version": "0.7.3-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts index 7e6ac73054..b635878f92 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts @@ -44,6 +44,7 @@ export type GithubLocationAnalyzerOptions = { tokenManager?: TokenManager; auth?: AuthService; githubCredentialsProvider?: GithubCredentialsProvider; + catalog?: CatalogApi; }; /** @public */ @@ -54,7 +55,8 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { private readonly auth: AuthService; constructor(options: GithubLocationAnalyzerOptions) { - this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); + this.catalogClient = + options.catalog ?? new CatalogClient({ discoveryApi: options.discovery }); this.integrations = ScmIntegrations.fromConfig(options.config); this.githubCredentialsProvider = options.githubCredentialsProvider || diff --git a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts index 809a1a5094..15d36a3011 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts @@ -21,6 +21,7 @@ import { import { catalogAnalysisExtensionPoint, catalogProcessingExtensionPoint, + catalogServiceRef, } from '@backstage/plugin-catalog-node/alpha'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { GithubEntityProvider } from '../providers/GithubEntityProvider'; @@ -37,34 +38,37 @@ export const githubCatalogModule = createBackendModule({ register(env) { env.registerInit({ deps: { - analyzers: catalogAnalysisExtensionPoint, + catalogAnalyzers: catalogAnalysisExtensionPoint, auth: coreServices.auth, - catalog: catalogProcessingExtensionPoint, + catalogProcessing: catalogProcessingExtensionPoint, config: coreServices.rootConfig, discovery: coreServices.discovery, events: eventsServiceRef, logger: coreServices.logger, scheduler: coreServices.scheduler, + catalog: catalogServiceRef, }, async init({ - catalog, + catalogProcessing, config, events, logger, scheduler, - analyzers, + catalogAnalyzers, discovery, auth, + catalog, }) { - analyzers.addScmLocationAnalyzer( + catalogAnalyzers.addScmLocationAnalyzer( new GithubLocationAnalyzer({ discovery, config, auth, + catalog, }), ); - catalog.addEntityProvider( + catalogProcessing.addEntityProvider( GithubEntityProvider.fromConfig(config, { events, logger, diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 321c7e1732..f998ea829b 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.2 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.2-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.2.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index a97b1bffb1..def95e0634 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 6740abb6a6..75d722d527 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.4.2-next.2 + +### Patch Changes + +- 0476be3: Add the `relations` array to allow Backstage to mirror GitLab's membership behavior, including descendant, inherited, and shared-from-group memberships. + + The previous `allowInherited` config option will be deprecated in future versions. Use the `relations` array with the `INHERITED` option instead. + + ```yaml + catalog: + providers: + gitlab: + development: + relations: + - INHERITED + ``` + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.4.2-next.1 + +### Patch Changes + +- 53b24d9: Internal update to use the new cache manager +- Updated dependencies + - @backstage/backend-defaults@0.5.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.4.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md index 8a21e237b5..0e6f39cae4 100644 --- a/plugins/catalog-backend-module-gitlab/api-report.md +++ b/plugins/catalog-backend-module-gitlab/api-report.md @@ -107,6 +107,7 @@ export type GitlabProviderConfig = { userPattern: RegExp; groupPattern: RegExp; allowInherited?: boolean; + relations?: string[]; orgEnabled?: boolean; schedule?: SchedulerServiceTaskScheduleDefinition; skipForkedRepos?: boolean; diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 4a890cf3bd..ef8d1f9148 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.4.2-next.0", + "version": "0.4.2-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts index 29914db705..01e8018845 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts @@ -27,10 +27,7 @@ import { } from '@backstage/plugin-catalog-node'; import { GitLabClient, GitLabProject, paginated } from './lib'; import { CacheService, LoggerService } from '@backstage/backend-plugin-api'; -import { - CacheManager, - PluginCacheManager, -} from '@backstage/backend-defaults/cache'; +import { CacheManager } from '@backstage/backend-defaults/cache'; /** * Extracts repositories out of an GitLab instance. @@ -64,13 +61,13 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { private constructor(options: { integrations: ScmIntegrationRegistry; - pluginCache: PluginCacheManager; + pluginCache: CacheService; logger: LoggerService; skipReposWithoutExactFileMatch?: boolean; skipForkedRepos?: boolean; }) { this.integrations = options.integrations; - this.cache = options.pluginCache.getClient(); + this.cache = options.pluginCache; this.logger = options.logger; this.skipReposWithoutExactFileMatch = options.skipReposWithoutExactFileMatch || false; diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 61f76d03de..ffd33ca5d9 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -191,10 +191,41 @@ export type GitlabProviderConfig = { groupPattern: RegExp; /** - * If true, the provider will also ingest add inherited users to the ingested groups - */ + * If true, the provider will also add inherited (ascendant) users to the ingested groups. + * See: https://docs.gitlab.com/ee/api/graphql/reference/#groupmemberrelation + * + * @deprecated Use the `relations` array to configure group membership relations instead. + **/ allowInherited?: boolean; + /** + * Specifies the types of group membership relations that should be included when ingesting data. + * + * The following values are valid: + * - 'DIRECT': Direct members of the group. This is the default relation and is always included. + * - 'INHERITED': Members inherited from parent (ascendant) groups. + * - 'DESCENDANTS': Members from child (descendant) groups. + * - 'SHARED_FROM_GROUPS': Members shared from other groups. + * + * See: https://docs.gitlab.com/ee/api/graphql/reference/#groupmemberrelation + * + * If the `relations` array is provided in the app-config.yaml, it should contain any combination of the above values. + * The 'DIRECT' relation is automatically included and cannot be excluded, even if not specified. + * Example configuration: + * + * ```yaml + * catalog: + * providers: + * gitlab: + * development: + * relations: + * - INHERITED + * - DESCENDANTS + * - SHARED_FROM_GROUPS + * ``` + */ + relations?: string[]; + orgEnabled?: boolean; schedule?: SchedulerServiceTaskScheduleDefinition; /** diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index 7f06079f42..d242f90e68 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -445,9 +445,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { let groupUsers: PagedResponse = { items: [] }; try { - const relations = this.config.allowInherited - ? ['DIRECT', 'INHERITED'] - : ['DIRECT']; + const relations = this.getRelations(this.config); groupUsers = await this.gitLabClient.getGroupMembers( group.full_path, relations, @@ -724,9 +722,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { return; } - const relations = this.config.allowInherited - ? ['DIRECT', 'INHERITED'] - : ['DIRECT']; + const relations = this.getRelations(this.config); // fetch group members from GitLab const groupMembers = await this.gitLabClient.getGroupMembers( @@ -802,4 +798,15 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { entity, ) as Entity; } + + private getRelations(config: any) { + if (Array.isArray(config.relations)) { + // filter out duplicates + const relationsSet = new Set(['DIRECT', ...config.relations]); + return Array.from(relationsSet); + } + + // TODO: remove this fallback in the next major version by ensuring the method returns only `['DIRECT']` if no `relations` array is provided. + return ['DIRECT', ...(config.allowInherited ? ['INHERITED'] : [])]; + } } diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts index 28df8c88b7..bb493278c9 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts @@ -58,6 +58,7 @@ describe('config', () => { userPattern: /[\s\S]*/, orgEnabled: false, allowInherited: false, + relations: [], schedule: undefined, skipForkedRepos: false, excludeRepos: [], @@ -98,6 +99,7 @@ describe('config', () => { userPattern: /[\s\S]*/, orgEnabled: false, allowInherited: false, + relations: [], schedule: undefined, skipForkedRepos: false, excludeRepos: [], @@ -139,6 +141,7 @@ describe('config', () => { userPattern: /[\s\S]*/, orgEnabled: false, allowInherited: false, + relations: [], schedule: undefined, restrictUsersToGroup: false, excludeRepos: [], @@ -181,6 +184,7 @@ describe('config', () => { userPattern: /[\s\S]*/, orgEnabled: false, allowInherited: false, + relations: [], schedule: undefined, restrictUsersToGroup: false, skipForkedRepos: false, @@ -224,6 +228,7 @@ describe('config', () => { userPattern: /[\s\S]*/, orgEnabled: false, allowInherited: false, + relations: [], skipForkedRepos: false, restrictUsersToGroup: false, excludeRepos: [], diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index c0fac9a166..f1883213da 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -45,6 +45,8 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { const orgEnabled: boolean = config.getOptionalBoolean('orgEnabled') ?? false; const allowInherited: boolean = config.getOptionalBoolean('allowInherited') ?? false; + const relations: string[] = config.getOptionalStringArray('relations') ?? []; + const skipForkedRepos: boolean = config.getOptionalBoolean('skipForkedRepos') ?? false; const excludeRepos: string[] = @@ -71,6 +73,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { schedule, orgEnabled, allowInherited, + relations, skipForkedRepos, excludeRepos, restrictUsersToGroup, diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 80bbdc5e7d..368b3aa5cb 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @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/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + ## 0.5.3-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 644a533b6b..4e964ccc9d 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.5.3-next.0", + "version": "0.5.3-next.2", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 7255cb6b68..0bba974ba3 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @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 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + ## 0.9.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 653104dfd1..e3521d0ef4 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.9.0-next.0", + "version": "0.9.0-next.2", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index f8d48f5f68..bdb2f2a706 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.1.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 412ffe60d3..4d7278ea2e 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", - "version": "0.1.0-next.0", + "version": "0.1.0-next.2", "description": "A module that subscribes to catalog releated events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 5424f8e00e..86fcccd872 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.6.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + ## 0.6.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 49a0813462..ebdd9bc316 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.6.2-next.0", + "version": "0.6.2-next.2", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 95bd5f212e..d5f138588a 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-catalog-backend@1.26.0-next.2 + - @backstage/integration@1.15.0-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-catalog-backend@1.25.3-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + ## 0.2.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index cf5dd7a52f..f5d519ef5e 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 49a82b1575..5ba5266b45 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @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 + - @backstage/plugin-catalog-node@1.12.7-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 83913a20b7..670dbe23a2 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.2.2-next.0", + "version": "0.2.2-next.2", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index 4fbe07a9a4..b5d52cd925 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.5 + ## 0.2.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 444a9ab2af..d64b3b468e 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.2.0-next.0", + "version": "0.2.0-next.2", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index a606984ef1..d0cfd61e39 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.5.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/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + +## 0.5.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/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + - @backstage/plugin-permission-common@0.8.1 + ## 0.5.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index f6f1154917..43fad5bd0e 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.5.0-next.0", + "version": "0.5.0-next.2", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 6128d697e3..1cef9786dd 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-catalog-backend +## 1.26.0-next.2 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- 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/integration@1.15.0-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.2 + +## 1.25.3-next.1 + +### Patch Changes + +- 1882cfe: Moved `getEntities` ordering to utilize database instead of having it inside catalog client + + Please note that the latest version of `@backstage/catalog-client` will not order the entities in the same way as before. This is because the ordering is now done in the database query instead of in the client. If you rely on the ordering of the entities, you may need to update your backend plugin or code to handle this change. + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-openapi-utils@0.1.18-next.1 + - @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/integration@1.14.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + - @backstage/plugin-search-backend-module-catalog@0.2.2-next.1 + ## 1.25.3-next.0 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 6261cacf20..4826758d01 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.25.3-next.0", + "version": "1.26.0-next.2", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", @@ -112,7 +112,7 @@ "better-sqlite3": "^11.0.0", "luxon": "^3.0.0", "msw": "^1.0.0", - "supertest": "^6.1.3", + "supertest": "^7.0.0", "wait-for-expect": "^3.0.2", "winston": "^3.13.0" }, diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index 9cc83098b3..a02c74c0c1 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -196,6 +196,7 @@ export interface QueryEntitiesInitialRequest { credentials: BackstageCredentials; fields?: (entity: Entity) => Entity; limit?: number; + offset?: number; filter?: EntityFilter; orderFields?: EntityOrder[]; fullTextFilter?: { diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 550db58f2e..f3bd46c05c 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1149,6 +1149,9 @@ export const spec = { { $ref: '#/components/parameters/limit', }, + { + $ref: '#/components/parameters/offset', + }, { $ref: '#/components/parameters/orderField', }, diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index 75dc1d9bbc..b02f561bc3 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -885,6 +885,7 @@ paths: parameters: - $ref: '#/components/parameters/fields' - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/offset' - $ref: '#/components/parameters/orderField' - $ref: '#/components/parameters/cursor' - $ref: '#/components/parameters/filter' diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index f9f731e3e4..c71f242969 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -495,6 +495,12 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { ]); } + if ( + isQueryEntitiesInitialRequest(request) && + request.offset !== undefined + ) { + dbQuery.offset(request.offset); + } // fetch an extra item to check if there are more items. dbQuery.limit(isFetchingBackwards ? limit : limit + 1); diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index d2b25fe226..4a89cfc6cb 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -158,6 +158,7 @@ export async function createRouter( const { items, pageInfo, totalItems } = await entitiesCatalog.queryEntities({ limit: req.query.limit, + offset: req.query.offset, ...parseQueryEntitiesParams(req.query), credentials: await httpAuth.credentials(req), }); diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 8a7784877e..dd17d300a5 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-catalog-graph +## 0.4.9-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- 8a474f2: Updating docs to use `createFrontendModule` instead +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## 0.4.9-next.1 + +### Patch Changes + +- da91078: Fixed a bug in the `CatalogGraphPage` component where, after clicking on some nodes, clicking the back button would break the navigation. This issue caused the entire navigation to fail and behaved differently across various browsers. +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + ## 0.4.9-next.0 ### Patch Changes diff --git a/plugins/catalog-graph/README-alpha.md b/plugins/catalog-graph/README-alpha.md index 784066c44f..06de696dcc 100644 --- a/plugins/catalog-graph/README-alpha.md +++ b/plugins/catalog-graph/README-alpha.md @@ -198,23 +198,28 @@ Overriding the card extension allows you to modify how it is implemented. Here is an example overriding the card extension with a custom component: ```tsx -import { createExtensionOverrides, createSchemaFromZod } from '@backstage/backstage-plugin-api'; +import { + createFrontendModule, + createSchemaFromZod, +} from '@backstage/backstage-plugin-api'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; -export default createExtensionOverrides( +export default createFrontendModule({ + pluginId: 'catalog-graph', extensions: [ createEntityCardExtension({ - // These namespace and name are necessary so the system knows that this extension will replace the default 'entity-relations' card extension provided by the 'catalog-graph' plugin - namespace: 'catalog-graph', name: 'entity-relations', - configSchema: createSchemaFromZod(z => z.object({ - filter: z.string().optional(), - // Ommitting the rest of default configs for simplicity in this example - })), - loader: () => import('./components').then(m => ) - }) - ] -); + configSchema: createSchemaFromZod(z => + z.object({ + filter: z.string().optional(), + // Ommitting the rest of default configs for simplicity in this example + }), + ), + loader: () => + import('./components').then(m => ), + }), + ], +}); ``` For more information about where to place extension overrides, see the official [documentation](https://backstage.io/docs/frontend-system/architecture/extension-overrides). @@ -292,16 +297,15 @@ Overriding the page extension allows you to modify how it is implemented. Here is example overriding the page extension with a custom component: ```tsx -import { createExtensionOverrides, createPageExtension, createSchemaFromZod } from '@backstage/backstage-plugin-api'; +import { createFrontendModule, createPageExtension, createSchemaFromZod } from '@backstage/backstage-plugin-api'; import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { catalogGraphRouteRef } from '@backstage/plugin-catalog-graph'; -export default createExtensionOverrides( +export default createFrontendModule({ + pluginId: 'catalog-graph', extensions: [ createPageExtension({ // Ommiting name since it is an index page - // This namespace is necessary so the system knows that this extension will replace the default 'catalog-graph' page extension - namespace: 'catalog-graph', defaultPath: '/catalog-graph', routeRef: convertLegacyRouteRef(catalogGraphRouteRef), createSchemaFromZod(z => z.object({ @@ -311,7 +315,7 @@ export default createExtensionOverrides( loader: () => import('./components').then(m => ) }) ] -); +}); ``` For more information about where to place extension overrides, see the official [documentation](https://backstage.io/docs/frontend-system/architecture/extension-overrides). diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 9c59eb1b08..4e83990c95 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.9-next.0", + "version": "0.4.9-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", @@ -75,7 +75,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0" }, "peerDependencies": { diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.ts b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.ts deleted file mode 100644 index a89fa353d6..0000000000 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.ts +++ /dev/null @@ -1,147 +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 { RELATION_MEMBER_OF } from '@backstage/catalog-model'; -import { act, renderHook } from '@testing-library/react'; -import { useLocation as useLocationMocked } from 'react-router-dom'; -import { Direction } from '../EntityRelationsGraph'; -import { useCatalogGraphPage } from './useCatalogGraphPage'; - -jest.mock('react-router-dom', () => ({ - useLocation: jest.fn(), -})); - -jest.spyOn(window.history, 'replaceState'); -jest.spyOn(window.history, 'pushState'); - -const useLocation = useLocationMocked as jest.Mock< - ReturnType ->; -const windowHistoryReplaceState = window.history.replaceState as jest.Mock< - ReturnType ->; -const windowHistoryPushState = window.history.pushState as jest.Mock< - ReturnType ->; - -describe('useCatalogGraphPage', () => { - beforeEach(() => { - useLocation.mockReturnValue({ - search: '?', - state: {}, - key: '', - pathname: '', - hash: '', - }); - }); - - afterEach(() => jest.resetAllMocks()); - - test('should use initial state', () => { - const { result } = renderHook(() => - useCatalogGraphPage({ - initialState: { - rootEntityRefs: ['b:d/c'], - maxDepth: 2, - direction: Direction.RIGHT_LEFT, - mergeRelations: false, - unidirectional: false, - showFilters: false, - selectedKinds: ['API'], - selectedRelations: [RELATION_MEMBER_OF], - }, - }), - ); - - expect(result.current.rootEntityNames).toEqual([ - { kind: 'b', namespace: 'd', name: 'c' }, - ]); - expect(result.current.maxDepth).toEqual(2); - expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); - expect(result.current.mergeRelations).toEqual(false); - expect(result.current.unidirectional).toEqual(false); - expect(result.current.showFilters).toEqual(false); - expect(result.current.selectedKinds).toEqual(['api']); - expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); - }); - - test('should use state from url', () => { - useLocation.mockReturnValueOnce({ - search: - '?rootEntityRefs[]=b:d/c&maxDepth=2&direction=RL&mergeRelations=false&unidirectional=false&showFilters=false&selectedKinds[]=api&selectedRelations[]=memberOf', - state: {}, - key: '', - pathname: '', - hash: '', - }); - - const { result } = renderHook(() => useCatalogGraphPage({})); - - expect(result.current.rootEntityNames).toEqual([ - { kind: 'b', namespace: 'd', name: 'c' }, - ]); - expect(result.current.maxDepth).toEqual(2); - expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); - expect(result.current.mergeRelations).toEqual(false); - expect(result.current.unidirectional).toEqual(false); - expect(result.current.showFilters).toEqual(false); - expect(result.current.selectedKinds).toEqual(['api']); - expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); - }); - - test('should update state in url (replace if setting changes)', () => { - const { result } = renderHook(() => useCatalogGraphPage({})); - - act(() => result.current.setMaxDepth(5)); - - expect(windowHistoryReplaceState).toHaveBeenCalledWith( - null, - '', - '/?maxDepth=5&unidirectional=true&mergeRelations=true&direction=LR&showFilters=true', - ); - - act(() => result.current.setUnidirectional(false)); - - expect(windowHistoryReplaceState).toHaveBeenCalledWith( - null, - '', - '/?maxDepth=5&unidirectional=false&mergeRelations=true&direction=LR&showFilters=true', - ); - }); - - test('should update state in url (only push if different root entity)', () => { - const { result, rerender } = renderHook(() => - useCatalogGraphPage({ - initialState: { - rootEntityRefs: ['component:default/first'], - }, - }), - ); - - act(() => - result.current.setRootEntityNames([ - { kind: 'Component', namespace: 'default', name: 'my' }, - ]), - ); - - rerender(); - - expect(windowHistoryPushState).toHaveBeenCalledWith( - null, - '', - '/?rootEntityRefs%5B%5D=component%3Adefault%2Fmy&maxDepth=%E2%88%9E&unidirectional=true&mergeRelations=true&direction=LR&showFilters=true', - ); - }); -}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.tsx new file mode 100644 index 0000000000..85cb606482 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.test.tsx @@ -0,0 +1,182 @@ +/* + * 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 { RELATION_MEMBER_OF } from '@backstage/catalog-model'; +import { renderHook, waitFor } from '@testing-library/react'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { BrowserRouter } from 'react-router-dom'; +import { Direction } from '../EntityRelationsGraph'; +import { useCatalogGraphPage } from './useCatalogGraphPage'; + +const wrapper = ({ children }: { children?: React.ReactNode }) => { + return {children}; +}; + +describe('useCatalogGraphPage', () => { + test('should use initial state', () => { + const { result } = renderHook(props => useCatalogGraphPage(props), { + initialProps: { + initialState: { + rootEntityRefs: ['b:d/c'], + maxDepth: 2, + direction: Direction.RIGHT_LEFT, + mergeRelations: false, + unidirectional: false, + showFilters: false, + selectedKinds: ['API'], + selectedRelations: [RELATION_MEMBER_OF], + }, + }, + wrapper, + }); + + expect(result.current.rootEntityNames).toEqual([ + { kind: 'b', namespace: 'd', name: 'c' }, + ]); + expect(result.current.maxDepth).toEqual(2); + expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); + expect(result.current.mergeRelations).toEqual(false); + expect(result.current.unidirectional).toEqual(false); + expect(result.current.showFilters).toEqual(false); + expect(result.current.selectedKinds).toEqual(['api']); + expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); + }); + + test('should use state from url', () => { + act(() => { + history.pushState( + {}, + '', + '?rootEntityRefs[]=b:d/c&maxDepth=2&direction=RL&mergeRelations=false&unidirectional=false&showFilters=false&selectedKinds[]=api&selectedRelations[]=memberOf', + ); + }); + + const { result } = renderHook(props => useCatalogGraphPage(props), { + initialProps: {}, + wrapper, + }); + + expect(result.current.rootEntityNames).toEqual([ + { kind: 'b', namespace: 'd', name: 'c' }, + ]); + expect(result.current.maxDepth).toEqual(2); + expect(result.current.direction).toEqual(Direction.RIGHT_LEFT); + expect(result.current.mergeRelations).toEqual(false); + expect(result.current.unidirectional).toEqual(false); + expect(result.current.showFilters).toEqual(false); + expect(result.current.selectedKinds).toEqual(['api']); + expect(result.current.selectedRelations).toEqual([RELATION_MEMBER_OF]); + }); + + test('should update state in url (replace if setting changes)', () => { + const { result } = renderHook(props => useCatalogGraphPage(props), { + wrapper, + initialProps: {}, + }); + + act(() => result.current.setMaxDepth(5)); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=b%3Ad%2Fc&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + + act(() => result.current.setUnidirectional(false)); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=b%3Ad%2Fc&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + }); + + test('should update state in url (only push if different root entity)', () => { + const oldLength = window.history.length; + const { result } = renderHook(props => useCatalogGraphPage(props), { + initialProps: { + initialState: { + rootEntityRefs: ['component:default/first'], + }, + }, + wrapper, + }); + + act(() => + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'my' }, + ]), + ); + + expect(window.history.length).toEqual(oldLength + 1); + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=component%3Adefault%2Fmy&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + }); + + test('should update state to last state on back', async () => { + const { result } = renderHook(props => useCatalogGraphPage(props), { + wrapper, + initialProps: { + initialState: { + rootEntityRefs: ['component:default/first'], + }, + }, + }); + + act(() => + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'first' }, + ]), + ); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=component%3Adefault%2Ffirst&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + + act(() => + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'second' }, + ]), + ); + + expect(window.location.search).toEqual( + '?rootEntityRefs%5B%5D=component%3Adefault%2Fsecond&maxDepth=5&selectedKinds%5B%5D=api&selectedRelations%5B%5D=memberOf&unidirectional=false&mergeRelations=false&direction=RL&showFilters=false&curve=curveMonotoneX', + ); + + act(() => { + result.current.setRootEntityNames([ + { kind: 'component', namespace: 'default', name: 'third' }, + ]); + }); + + act(() => { + window.history.back(); + }); + + await waitFor(() => { + expect(result.current.rootEntityNames).toEqual([ + { kind: 'component', namespace: 'default', name: 'second' }, + ]); + }); + + act(() => { + window.history.back(); + }); + + await waitFor(() => { + expect(result.current.rootEntityNames).toEqual([ + { kind: 'component', namespace: 'default', name: 'first' }, + ]); + }); + }); +}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts index 6e47dedc50..1f6fb74997 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/useCatalogGraphPage.ts @@ -27,13 +27,12 @@ import { useMemo, useState, } from 'react'; -import { useLocation } from 'react-router-dom'; -import usePrevious from 'react-use/esm/usePrevious'; +import { useLocation, useNavigate } from 'react-router-dom'; import { Direction } from '../EntityRelationsGraph'; export type CatalogGraphPageValue = { rootEntityNames: CompoundEntityRef[]; - setRootEntityNames: Dispatch>; + setRootEntityNames: (value: CompoundEntityRef[]) => void; maxDepth: number; setMaxDepth: Dispatch>; selectedRelations: string[] | undefined; @@ -70,6 +69,8 @@ export function useCatalogGraphPage({ }; }): CatalogGraphPageValue { const location = useLocation(); + const navigate = useNavigate(); + const query = useMemo( () => (qs.parse(location.search, { arrayLimit: 0, ignoreQueryPrefix: true }) || @@ -87,19 +88,46 @@ export function useCatalogGraphPage({ [location.search], ); - // Initial state - const [rootEntityNames, setRootEntityNames] = useState( + const rootEntityNames = useMemo( () => (Array.isArray(query.rootEntityRefs) ? query.rootEntityRefs : initialState?.rootEntityRefs ?? [] ).map(r => parseEntityRef(r)), + [initialState?.rootEntityRefs, query.rootEntityRefs], ); + + const setRootEntityNames = useCallback( + (value: CompoundEntityRef[]) => { + const areSame = + rootEntityNames.length === value.length && + rootEntityNames.every( + (r, i) => stringifyEntityRef(r) === stringifyEntityRef(value[i]), + ); + + if (areSame) { + return; + } + + const newSearch = qs.stringify( + { + ...query, + rootEntityRefs: value.map(r => stringifyEntityRef(r)), + }, + { arrayFormat: 'brackets', addQueryPrefix: true }, + ); + + navigate(newSearch); + }, + [rootEntityNames, navigate, query], + ); + const [maxDepth, setMaxDepth] = useState(() => typeof query.maxDepth === 'string' ? parseMaxDepth(query.maxDepth) : initialState?.maxDepth ?? Number.POSITIVE_INFINITY, ); + const [selectedRelations, setSelectedRelations] = useState< string[] | undefined >(() => @@ -107,105 +135,53 @@ export function useCatalogGraphPage({ ? query.selectedRelations : initialState?.selectedRelations, ); + const [selectedKinds, setSelectedKinds] = useState(() => (Array.isArray(query.selectedKinds) ? query.selectedKinds : initialState?.selectedKinds )?.map(k => k.toLocaleLowerCase('en-US')), ); + const [unidirectional, setUnidirectional] = useState(() => typeof query.unidirectional === 'string' ? query.unidirectional === 'true' : initialState?.unidirectional ?? true, ); + const [mergeRelations, setMergeRelations] = useState(() => typeof query.mergeRelations === 'string' ? query.mergeRelations === 'true' : initialState?.mergeRelations ?? true, ); + const [direction, setDirection] = useState(() => typeof query.direction === 'string' ? query.direction : initialState?.direction ?? Direction.LEFT_RIGHT, ); + const [curve, setCurve] = useState<'curveStepBefore' | 'curveMonotoneX'>(() => typeof query.curve === 'string' ? query.curve : initialState?.curve ?? 'curveMonotoneX', ); + const [showFilters, setShowFilters] = useState(() => typeof query.showFilters === 'string' ? query.showFilters === 'true' : initialState?.showFilters ?? true, ); + const toggleShowFilters = useCallback( () => setShowFilters(s => !s), [setShowFilters], ); - // Update from query parameters - const prevQueryParams = usePrevious(location.search); useEffect(() => { - // Only respond to changes to url query params - if (location.search === prevQueryParams) { - return; - } - - if (Array.isArray(query.rootEntityRefs)) { - setRootEntityNames(query.rootEntityRefs.map(r => parseEntityRef(r))); - } - - if (typeof query.maxDepth === 'string') { - setMaxDepth(parseMaxDepth(query.maxDepth)); - } - - if (Array.isArray(query.selectedKinds)) { - setSelectedKinds(query.selectedKinds); - } - - if (Array.isArray(query.selectedRelations)) { - setSelectedRelations(query.selectedRelations); - } - - if (typeof query.unidirectional === 'string') { - setUnidirectional(query.unidirectional === 'true'); - } - - if (typeof query.mergeRelations === 'string') { - setMergeRelations(query.mergeRelations === 'true'); - } - - if (typeof query.direction === 'string') { - setDirection(query.direction); - } - - if (typeof query.showFilters === 'string') { - setShowFilters(query.showFilters === 'true'); - } - }, [ - prevQueryParams, - location.search, - query, - setRootEntityNames, - setMaxDepth, - setSelectedKinds, - setSelectedRelations, - setUnidirectional, - setMergeRelations, - setDirection, - setShowFilters, - ]); - - // Update query parameters - const previousRootEntityRefs = usePrevious( - rootEntityNames.map(e => stringifyEntityRef(e)), - ); - - useEffect(() => { - const rootEntityRefs = rootEntityNames.map(e => stringifyEntityRef(e)); const newParams = qs.stringify( { - rootEntityRefs, + rootEntityRefs: rootEntityNames.map(stringifyEntityRef), maxDepth: isFinite(maxDepth) ? maxDepth : '∞', selectedKinds, selectedRelations, @@ -213,36 +189,23 @@ export function useCatalogGraphPage({ mergeRelations, direction, showFilters, + curve, }, { arrayFormat: 'brackets', addQueryPrefix: true }, ); - 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. - // Only push a new history entry if we switched to another entity, but not - // if we just changed a viewer setting. - if ( - !previousRootEntityRefs || - (rootEntityRefs.length === previousRootEntityRefs.length && - rootEntityRefs.every((v, i) => v === previousRootEntityRefs[i])) - ) { - window.history.replaceState(null, document.title, newUrl); - } else { - window.history.pushState(null, document.title, newUrl); - } + navigate(newParams, { replace: true }); }, [ - rootEntityNames, maxDepth, + curve, selectedKinds, selectedRelations, unidirectional, mergeRelations, direction, showFilters, - previousRootEntityRefs, + rootEntityNames, + navigate, ]); return { diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index c5dcfd4e11..318af495d3 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-catalog-import +## 0.12.3-next.2 + +### Patch Changes + +- f3c90da: Support button title should be contained in a single menu item +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/integration@1.15.0-next.0 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.12.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + ## 0.12.3-next.0 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index e0e7056e47..964dbd3c33 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.12.3-next.0", + "version": "0.12.3-next.2", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", @@ -86,7 +86,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, diff --git a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx index 680ce16ae4..fa89ac2caf 100644 --- a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx +++ b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx @@ -40,6 +40,8 @@ export const DefaultImportPage = () => { const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const appTitle = configApi.getOptionalString('app.title') || 'Backstage'; + const supportTitle = `Start tracking your component in ${appTitle} by adding it to the software catalog.`; + const contentItems = [ @@ -55,10 +57,7 @@ export const DefaultImportPage = () => {
- - Start tracking your component in {appTitle} by adding it to the - software catalog. - + {supportTitle} diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 89ed287403..f169358f31 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-node +## 1.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## 1.12.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + ## 1.12.7-next.0 ### Patch Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 20124ceca5..32cb210f97 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.12.7-next.0", + "version": "1.12.7-next.2", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 7f01541057..9cfe0b93f5 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-catalog-react +## 1.13.0-next.2 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 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-components@0.14.11-next.1 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + +## 1.12.4-next.1 + +### Patch Changes + +- ae9b6cb: Small internal fix to better work with recent `lodash` versions +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-react@0.4.25 + ## 1.12.4-next.0 ### Patch Changes diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 79aad53c33..274f1c0d68 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -307,8 +307,26 @@ export type EntityListContextProps< prev?: () => void; }; totalItems?: number; + limit: number; + offset?: number; + setLimit: (limit: number) => void; + setOffset?: (offset: number) => void; + paginationMode: PaginationMode; }; +// @public (undocumented) +export type EntityListPagination = + | boolean + | { + mode?: 'cursor'; + limit?: number; + } + | { + mode: 'offset'; + limit?: number; + offset?: number; + }; + // @public export const EntityListProvider: ( props: EntityListProviderProps, @@ -316,11 +334,7 @@ export const EntityListProvider: ( // @public (undocumented) export type EntityListProviderProps = PropsWithChildren<{ - pagination?: - | boolean - | { - limit?: number; - }; + pagination?: EntityListPagination; }>; // @public (undocumented) @@ -692,6 +706,9 @@ export class MockStarredEntitiesApi implements StarredEntitiesApi { toggleStarred(entityRef: string): Promise; } +// @public (undocumented) +export type PaginationMode = 'cursor' | 'offset' | 'none'; + // @public export interface StarredEntitiesApi { starredEntitie$(): Observable>; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 33f7772e81..dec15a5eec 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "1.12.4-next.0", + "version": "1.13.0-next.2", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", @@ -92,7 +92,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/zen-observable": "^0.8.0", "react-test-renderer": "^16.13.1" diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index ab674ff98e..f0d59247ad 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -16,26 +16,17 @@ import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import IconButton from '@material-ui/core/IconButton'; -import Tooltip from '@material-ui/core/Tooltip'; -import { withStyles } from '@material-ui/core/styles'; -import Star from '@material-ui/icons/Star'; -import StarBorder from '@material-ui/icons/StarBorder'; import React, { ComponentProps } from 'react'; import { useStarredEntity } from '../../hooks/useStarredEntity'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { FavoriteToggle } from '@backstage/core-components'; /** @public */ export type FavoriteEntityProps = ComponentProps & { entity: Entity; }; -const YellowStar = withStyles({ - root: { - color: '#f3ba37', - }, -})(Star); - /** * IconButton for showing if a current entity is starred and adding/removing it from the favorite entities * @param props - MaterialUI IconButton props extended by required `entity` prop @@ -56,16 +47,12 @@ export const FavoriteEntity = (props: FavoriteEntityProps) => { )}`; return ( - toggleStarredEntity()} - > - - {isStarredEntity ? : } - - + /> ); }; diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index c6b515aa67..f572cec814 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -28,7 +28,7 @@ import MenuItem from '@material-ui/core/MenuItem'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import SettingsIcon from '@material-ui/icons/Settings'; -import StarIcon from '@material-ui/icons/Star'; +import { StarIcon } from '@backstage/core-components'; import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { EntityUserFilter } from '../../filters'; import { useEntityList } from '../../hooks'; diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index c3021f8992..befcce520d 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -33,6 +33,7 @@ export type { DefaultEntityFilters, EntityListContextProps, EntityListProviderProps, + PaginationMode, } from './useEntityListProvider'; export { useEntityTypeFilter } from './useEntityTypeFilter'; export { useRelatedEntities } from './useRelatedEntities'; diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 9afa81e2dd..762fba1897 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -31,7 +31,7 @@ import qs from 'qs'; import React, { PropsWithChildren } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { catalogApiRef } from '../api'; -import { starredEntitiesApiRef, MockStarredEntitiesApi } from '../apis'; +import { MockStarredEntitiesApi, starredEntitiesApiRef } from '../apis'; import { EntityKindFilter, EntityTextFilter, @@ -42,6 +42,7 @@ import { EntityListProvider, useEntityList } from './useEntityListProvider'; import { useMountEffect } from '@react-hookz/web'; import { translationApiRef } from '@backstage/core-plugin-api/alpha'; import { MockTranslationApi } from '@backstage/test-utils/alpha'; +import { EntityListPagination } from '../types'; const entities: Entity[] = [ { @@ -91,7 +92,7 @@ const mockCatalogApi: Partial> = { }; const createWrapper = - (options: { location?: string; pagination: boolean }) => + (options: { location?: string; pagination: EntityListPagination }) => (props: PropsWithChildren) => { const InitialFiltersWrapper = ({ children }: PropsWithChildren) => { const { updateFilters } = useEntityList(); @@ -556,3 +557,232 @@ describe('', () => { }); }); }); + +describe('', () => { + const origReplaceState = window.history.replaceState; + const pagination: EntityListPagination = { mode: 'offset' }; + const limit = 20; + const orderFields = [{ field: 'metadata.name', order: 'asc' }]; + + beforeEach(() => { + window.history.replaceState = jest.fn(); + }); + afterEach(() => { + window.history.replaceState = origReplaceState; + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('sends search text to the backend', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + initialProps: { + userFilter: 'all', + }, + }); + + act(() => + result.current.updateFilters({ + text: new EntityTextFilter('2'), + }), + ); + + await waitFor(() => { + expect(mockCatalogApi.getEntities).not.toHaveBeenCalledTimes(1); + expect(result.current.entities.length).toBe(1); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ + filter: { kind: 'component' }, + limit, + orderFields, + fullTextFilter: { + term: '2', + fields: [ + 'metadata.name', + 'metadata.title', + 'spec.profile.displayName', + ], + }, + }); + }); + }); + + it('should send backend filters', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + await waitFor(() => { + expect(result.current.backendEntities.length).toBe(2); + }); + + expect(result.current.entities.length).toBe(2); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith({ + filter: { kind: 'component' }, + limit, + orderFields, + }); + }); + + it('resolves frontend filters', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + initialProps: { + userFilter: 'all', + }, + }); + + act(() => + result.current.updateFilters({ + user: EntityUserFilter.owned(ownershipEntityRefs), + }), + ); + + await waitFor(() => { + expect(result.current.backendEntities.length).toBe(2); + expect(result.current.entities.length).toBe(1); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + }); + }); + + it('resolves query param filter values', async () => { + const query = qs.stringify({ + filters: { kind: 'component', type: 'service' }, + }); + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ + location: `/catalog?${query}`, + pagination, + }), + }); + + await waitFor(() => { + expect(result.current.queryParameters).toBeTruthy(); + }); + expect(result.current.queryParameters).toEqual({ + kind: 'component', + type: 'service', + }); + }); + + it('fetch when frontend filters change', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + await waitFor(() => { + expect(result.current.entities.length).toBe(2); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + }); + + act(() => + result.current.updateFilters({ + user: EntityUserFilter.owned(ownershipEntityRefs), + }), + ); + + await waitFor(() => { + expect(result.current.entities.length).toBe(1); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(2); + }); + }); + + it('fetch when limit change', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + await waitFor(() => { + expect(result.current.entities.length).toBe(2); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + }); + + act(() => result.current.setLimit(50)); + + await waitFor(() => { + expect(result.current.entities.length).toBe(2); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(2); + expect(result.current.limit).toEqual(50); + }); + }); + + it('debounces multiple filter changes', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + await waitFor(() => { + expect(result.current.backendEntities.length).toBeGreaterThan(0); + }); + expect(result.current.backendEntities.length).toBe(2); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + + await act(async () => { + result.current.updateFilters({ kind: new EntityKindFilter('api') }); + result.current.updateFilters({ type: new EntityTypeFilter('service') }); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenNthCalledWith(2, { + filter: { kind: 'api', 'spec.type': ['service'] }, + limit, + orderFields, + }); + }); + }); + + it('debounces multiple offset changes', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + await waitFor(() => { + expect(result.current.backendEntities.length).toBeGreaterThan(0); + }); + expect(result.current.backendEntities.length).toBe(2); + expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); + + await act(async () => { + result.current.setOffset!(5); + result.current.setOffset!(10); + }); + + await waitFor(() => { + expect(mockCatalogApi.queryEntities).toHaveBeenNthCalledWith(2, { + filter: { kind: 'component' }, + limit, + offset: 10, + orderFields, + }); + expect(result.current.offset).toEqual(10); + }); + }); + + it('returns an error on catalogApi failure', async () => { + const { result } = renderHook(() => useEntityList(), { + wrapper: createWrapper({ pagination }), + }); + + await waitFor(() => { + expect(result.current.backendEntities.length).toBeGreaterThan(0); + }); + expect(result.current.backendEntities.length).toBe(2); + + mockCatalogApi.queryEntities!.mockRejectedValueOnce('error'); + act(() => { + result.current.updateFilters({ kind: new EntityKindFilter('api') }); + }); + await waitFor(() => { + expect(result.current.error).toBeDefined(); + }); + }); +}); diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index da2a37381c..274ec52a18 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -34,16 +34,16 @@ import { EntityErrorFilter, EntityKindFilter, EntityLifecycleFilter, + EntityNamespaceFilter, EntityOrphanFilter, EntityOwnerFilter, EntityTagFilter, EntityTextFilter, EntityTypeFilter, - UserListFilter, - EntityNamespaceFilter, EntityUserFilter, + UserListFilter, } from '../filters'; -import { EntityFilter } from '../types'; +import { EntityFilter, EntityListPagination } from '../types'; import { reduceBackendCatalogFilters, reduceCatalogFilters, @@ -66,6 +66,9 @@ export type DefaultEntityFilters = { namespace?: EntityNamespaceFilter; }; +/** @public */ +export type PaginationMode = 'cursor' | 'offset' | 'none'; + /** @public */ export type EntityListContextProps< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, @@ -108,8 +111,12 @@ export type EntityListContextProps< next?: () => void; prev?: () => void; }; - totalItems?: number; + limit: number; + offset?: number; + setLimit: (limit: number) => void; + setOffset?: (offset: number) => void; + paginationMode: PaginationMode; }; /** @@ -127,13 +134,15 @@ type OutputState = { backendEntities: Entity[]; pageInfo?: QueryEntitiesResponse['pageInfo']; totalItems?: number; + offset?: number; + limit?: number; }; /** * @public */ export type EntityListProviderProps = PropsWithChildren<{ - pagination?: boolean | { limit?: number }; + pagination?: EntityListPagination; }>; /** @@ -155,31 +164,62 @@ export const EntityListProvider = ( // update of the URL or two catalog sidebar links with different catalog filters. const location = useLocation(); - const enablePagination = - props.pagination === true || typeof props.pagination === 'object'; + const getPaginationMode = (): PaginationMode => { + if (props.pagination === true) { + return 'cursor'; + } + return typeof props.pagination === 'object' + ? props.pagination.mode ?? 'cursor' + : 'none'; + }; - const limit = - props.pagination && - typeof props.pagination === 'object' && - typeof props.pagination.limit === 'number' - ? props.pagination.limit - : 20; + const paginationMode: PaginationMode = getPaginationMode(); + const paginationLimit = + typeof props.pagination === 'object' ? props.pagination.limit ?? 20 : 20; - const { queryParameters, cursor: initialCursor } = useMemo(() => { + const { + queryParameters, + cursor: initialCursor, + offset: initialOffset, + limit: initialLimit, + } = useMemo(() => { const parsed = qs.parse(location.search, { ignoreQueryPrefix: true, }); + let limit = paginationLimit; + if (typeof parsed.limit === 'string') { + const queryLimit = Number.parseInt(parsed.limit, 10); + if (!isNaN(queryLimit)) { + limit = queryLimit; + } + } + + const offset = + typeof parsed.offset === 'string' && paginationMode === 'offset' + ? Number.parseInt(parsed.offset, 10) + : undefined; + return { queryParameters: (parsed.filters ?? {}) as Record< string, string | string[] >, - cursor: typeof parsed.cursor === 'string' ? parsed.cursor : undefined, + cursor: + typeof parsed.cursor === 'string' && paginationMode === 'cursor' + ? parsed.cursor + : undefined, + offset: + paginationMode === 'offset' && offset && !isNaN(offset) + ? offset + : undefined, + limit, }; - }, [location]); + }, [paginationMode, location.search, paginationLimit]); const [cursor, setCursor] = useState(initialCursor); + const [offset, setOffset] = useState(initialOffset); + const [limit, setLimit] = useState(initialLimit); const [outputState, setOutputState] = useState>( () => { @@ -187,7 +227,9 @@ export const EntityListProvider = ( appliedFilters: {} as EntityFilters, entities: [], backendEntities: [], - pageInfo: enablePagination ? {} : undefined, + pageInfo: paginationMode === 'cursor' ? {} : undefined, + offset, + limit, }; }, ); @@ -212,7 +254,7 @@ export const EntityListProvider = ( {} as Record, ); - if (enablePagination) { + if (paginationMode !== 'none') { if (cursor) { if (cursor !== outputState.appliedCursor) { const entityFilter = reduceEntityFilters(compacted); @@ -236,10 +278,14 @@ export const EntityListProvider = ( compact(Object.values(outputState.appliedFilters)), ); - if (!isEqual(previousBackendFilter, backendFilter)) { + if ( + paginationMode === 'offset' || + !isEqual(previousBackendFilter, backendFilter) + ) { const response = await catalogApi.queryEntities({ ...backendFilter, limit, + offset, orderFields: [{ field: 'metadata.name', order: 'asc' }], }); setOutputState({ @@ -248,6 +294,8 @@ export const EntityListProvider = ( entities: response.items.filter(entityFilter), pageInfo: response.pageInfo, totalItems: response.totalItems, + limit, + offset, }); } } @@ -290,7 +338,7 @@ export const EntityListProvider = ( ignoreQueryPrefix: true, }); const newParams = qs.stringify( - { ...oldParams, filters: queryParams, cursor }, + { ...oldParams, filters: queryParams, cursor, offset, limit }, { addQueryPrefix: true, arrayFormat: 'repeat' }, ); const newUrl = `${window.location.pathname}${newParams}`; @@ -308,14 +356,16 @@ export const EntityListProvider = ( requestedFilters, outputState, cursor, - enablePagination, + paginationMode, + limit, + offset, ], { loading: true }, ); // Slight debounce on the refresh, since (especially on page load) several // filters will be calling this in rapid succession. - useDebounce(refresh, 10, [requestedFilters, cursor]); + useDebounce(refresh, 10, [requestedFilters, cursor, limit, offset]); const updateFilters = useCallback( ( @@ -339,7 +389,7 @@ export const EntityListProvider = ( ); const pageInfo = useMemo(() => { - if (!enablePagination) { + if (paginationMode !== 'cursor') { return undefined; } @@ -349,7 +399,7 @@ export const EntityListProvider = ( prev: prevCursor ? () => setCursor(prevCursor) : undefined, next: nextCursor ? () => setCursor(nextCursor) : undefined, }; - }, [enablePagination, outputState.pageInfo]); + }, [paginationMode, outputState.pageInfo]); const value = useMemo( () => ({ @@ -362,8 +412,25 @@ export const EntityListProvider = ( error, pageInfo, totalItems: outputState.totalItems, + limit, + offset, + setLimit, + setOffset, + paginationMode, }), - [outputState, updateFilters, queryParameters, loading, error, pageInfo], + [ + outputState, + updateFilters, + queryParameters, + loading, + error, + pageInfo, + limit, + offset, + paginationMode, + setLimit, + setOffset, + ], ); return ( diff --git a/plugins/catalog-react/src/testUtils/providers.tsx b/plugins/catalog-react/src/testUtils/providers.tsx index 6b88168207..9bb46dc6e5 100644 --- a/plugins/catalog-react/src/testUtils/providers.tsx +++ b/plugins/catalog-react/src/testUtils/providers.tsx @@ -73,6 +73,11 @@ export function MockEntityListContextProvider< error: value?.error, totalItems: value?.totalItems ?? (value?.entities ?? defaultValues.entities).length, + limit: value?.limit ?? 20, + offset: value?.offset, + setLimit: value?.setLimit ?? (() => {}), + setOffset: value?.setOffset, + paginationMode: value?.paginationMode ?? 'none', }), [value, defaultValues, filters, updateFilters], ); diff --git a/plugins/catalog-react/src/types.ts b/plugins/catalog-react/src/types.ts index 41c7d75f0d..57b21b634e 100644 --- a/plugins/catalog-react/src/types.ts +++ b/plugins/catalog-react/src/types.ts @@ -46,3 +46,9 @@ export type EntityFilter = { /** @public */ export type UserListFilterKind = 'owned' | 'starred' | 'all'; + +/** @public */ +export type EntityListPagination = + | boolean + | { mode?: 'cursor'; limit?: number } + | { mode: 'offset'; limit?: number; offset?: number }; diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 5b43a660d5..d50aa87d00 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.8-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 0.2.8-next.0 + +### Patch Changes + +- 4f08c85: Show additional info on DevTools unprocessed entities table + + - Location path (so that it's easier to search the failed entity from the YAML URL) + - Time info of last discovery and next refresh time so that users can be aware of it and can sort the errors based on the time. + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + ## 0.2.7 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 1c028e05a6..31cd27e5fa 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.7", + "version": "0.2.8-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", @@ -45,13 +45,14 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "luxon": "^3.5.0", "react-use": "^17.2.4" }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/dev-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0" + "@testing-library/react": "^16.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx b/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx index 5d1800cb29..65e6eb731e 100644 --- a/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx +++ b/plugins/catalog-unprocessed-entities/src/components/FailedEntities.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React, { useState } from 'react'; - +import { DateTime } from 'luxon'; import { ErrorPanel, MarkdownContent, @@ -88,6 +88,24 @@ const RenderErrorContext = ({ return null; }; +/** + * Converts input datetime which lacks timezone info into user's local time so that they can + * easily understand the times. + */ +const convertTimeToLocalTimezone = (strDateTime: string | Date) => { + const dateTime = DateTime.fromFormat( + strDateTime.toLocaleString(), + 'yyyy-MM-dd hh:mm:ss', + { + zone: 'UTC', + }, + ); + + const dateTimeLocalTz = dateTime.setZone(DateTime.local().zoneName); + + return dateTimeLocalTz.toFormat('yyyy-MM-dd hh:mm:ss ZZZZ'); +}; + export const FailedEntities = () => { const classes = useStyles(); const unprocessedApi = useApi(catalogUnprocessedEntitiesApiRef); @@ -140,6 +158,13 @@ export const FailedEntities = () => { render: (rowData: UnprocessedEntity | {}) => (rowData as UnprocessedEntity).entity_ref, }, + { + title: Location Path, + sorting: true, + field: 'location_key', + render: (rowData: UnprocessedEntity | {}) => + (rowData as UnprocessedEntity).location_key, + }, { title: Kind, sorting: true, @@ -156,7 +181,25 @@ export const FailedEntities = () => { 'unknown', }, { - title: Raw, + title: Last Discovery At, + sorting: true, + field: 'last_discovery_at', + render: (rowData: UnprocessedEntity | {}) => + convertTimeToLocalTimezone( + (rowData as UnprocessedEntity).last_discovery_at, + ) || 'unknown', + }, + { + title: Next Refresh At, + sorting: true, + field: 'next_update_at', + render: (rowData: UnprocessedEntity | {}) => + convertTimeToLocalTimezone( + (rowData as UnprocessedEntity).next_update_at, + ) || 'unknown', + }, + { + title: Raw Entity Definition, sorting: false, render: (rowData: UnprocessedEntity | {}) => ( diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 3d5de77a6b..071fe3a64b 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-catalog +## 1.23.0-next.2 + +### Minor Changes + +- 78475c3: Allow offset mode paging in entity list provider + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/integration-react@1.1.31-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/plugin-search-react@1.8.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-search-common@1.2.14 + +## 1.22.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-search-react@1.8.0-next.1 + ## 1.22.1-next.0 ### Patch Changes diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 541cb08887..2c1d2597fe 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -12,6 +12,7 @@ import { ComponentEntity } from '@backstage/catalog-model'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { EntityListContextProps } from '@backstage/plugin-catalog-react'; +import { EntityListPagination } from '@backstage/plugin-catalog-react'; import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { EntityPresentationApi } from '@backstage/plugin-catalog-react'; import { EntityRefPresentation } from '@backstage/plugin-catalog-react'; @@ -244,11 +245,7 @@ export interface DefaultCatalogPageProps { // (undocumented) ownerPickerMode?: EntityOwnerPickerProps['mode']; // (undocumented) - pagination?: - | boolean - | { - limit?: number; - }; + pagination?: EntityListPagination; // (undocumented) tableOptions?: TableProps['options']; } diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 2212eb037d..317e91fa4d 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.22.1-next.0", + "version": "1.23.0-next.2", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", @@ -95,7 +95,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/pluralize": "^0.0.33", "swr": "^2.2.5" diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index a46a552b8d..366427d33d 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -26,26 +26,26 @@ import { import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { CatalogFilterLayout, + DefaultFilters, + EntityListPagination, EntityListProvider, - UserListFilterKind, EntityOwnerPickerProps, + UserListFilterKind, } from '@backstage/plugin-catalog-react'; import React, { ReactNode } from 'react'; import { createComponentRouteRef } from '../../routes'; import { CatalogTable, CatalogTableRow } from '../CatalogTable'; import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; - import { CatalogTableColumnsFunc } from '../CatalogTable/types'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; import { usePermission } from '@backstage/plugin-permission-react'; -import { DefaultFilters } from '@backstage/plugin-catalog-react'; /** @internal */ export type BaseCatalogPageProps = { filters: ReactNode; content?: ReactNode; - pagination?: boolean | { limit?: number }; + pagination?: EntityListPagination; }; /** @internal */ @@ -95,9 +95,9 @@ export interface DefaultCatalogPageProps { tableOptions?: TableProps['options']; emptyContent?: ReactNode; ownerPickerMode?: EntityOwnerPickerProps['mode']; - pagination?: boolean | { limit?: number }; filters?: ReactNode; initiallySelectedNamespaces?: string[]; + pagination?: EntityListPagination; } export function DefaultCatalogPage(props: DefaultCatalogPageProps) { diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 1026dc5d26..2d5e78a0d6 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -35,21 +35,20 @@ import { useStarredEntities, } from '@backstage/plugin-catalog-react'; import Typography from '@material-ui/core/Typography'; -import { withStyles } from '@material-ui/core/styles'; import { visuallyHidden } from '@mui/utils'; import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; -import Star from '@material-ui/icons/Star'; -import StarBorder from '@material-ui/icons/StarBorder'; import { capitalize } from 'lodash'; import pluralize from 'pluralize'; import React, { ReactNode, useMemo } from 'react'; import { columnFactories } from './columns'; import { CatalogTableColumnsFunc, CatalogTableRow } from './types'; -import { PaginatedCatalogTable } from './PaginatedCatalogTable'; +import { OffsetPaginatedCatalogTable } from './OffsetPaginatedCatalogTable'; +import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha/translation'; +import { FavoriteToggleIcon } from '@backstage/core-components'; /** * Props for {@link CatalogTable}. @@ -64,12 +63,6 @@ export interface CatalogTableProps { subtitle?: string; } -const YellowStar = withStyles({ - root: { - color: '#f3ba37', - }, -})(Star); - const refCompare = (a: Entity, b: Entity) => { const toRef = (entity: Entity) => entity.metadata.title || @@ -90,9 +83,17 @@ export const CatalogTable = (props: CatalogTableProps) => { } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const entityListContext = useEntityList(); - const { loading, error, entities, filters, pageInfo, totalItems } = - entityListContext; - const enablePagination = !!pageInfo; + + const { + loading, + error, + entities, + filters, + pageInfo, + totalItems, + paginationMode, + } = entityListContext; + const tableColumns = useMemo( () => typeof columns === 'function' ? columns(entityListContext) : columns, @@ -160,12 +161,7 @@ export const CatalogTable = (props: CatalogTableProps) => { return { cellStyle: { paddingLeft: '1em' }, - icon: () => ( - <> - {title} - {isStarred ? : } - - ), + icon: () => , tooltip: title, onClick: () => toggleStarredEntity(entity), }; @@ -195,9 +191,24 @@ export const CatalogTable = (props: CatalogTableProps) => { ...tableOptions, }; - if (enablePagination) { + if (paginationMode === 'cursor') { return ( - + ); + } else if (paginationMode === 'offset') { + return ( + { subtitle={subtitle} options={options} data={entities.map(toEntityRow)} - next={pageInfo.next} - prev={pageInfo.prev} /> ); } diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.test.tsx similarity index 87% rename from plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx rename to plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.test.tsx index c96fe0e067..be250fddbf 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.test.tsx @@ -14,19 +14,18 @@ * limitations under the License. */ import React, { ReactNode } from 'react'; -import { fireEvent, waitFor } from '@testing-library/react'; -import { PaginatedCatalogTable } from './PaginatedCatalogTable'; -import { screen } from '@testing-library/react'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { CatalogTableRow } from './types'; import { renderInTestApp } from '@backstage/test-utils'; import { - EntityKindFilter, - MockEntityListContextProvider, DefaultEntityFilters, + EntityKindFilter, EntityListContextProps, + MockEntityListContextProvider, } from '@backstage/plugin-catalog-react'; -describe('PaginatedCatalogTable', () => { +describe('CursorPaginatedCatalogTable', () => { const data = new Array(100).fill(0).map((_, index) => { const name = `component-${index}`; return { @@ -65,7 +64,9 @@ describe('PaginatedCatalogTable', () => { it('should display all the items', async () => { await renderInTestApp( - wrapInContext(), + wrapInContext( + , + ), ); for (const item of data) { @@ -76,7 +77,7 @@ describe('PaginatedCatalogTable', () => { it('should display and invoke the next button', async () => { const { rerender } = await renderInTestApp( wrapInContext( - { rerender( wrapInContext( - , + , ), ); @@ -108,7 +109,7 @@ describe('PaginatedCatalogTable', () => { it('should display and invoke the prev button', async () => { const { rerender } = await renderInTestApp( wrapInContext( - { rerender( wrapInContext( - , + , ), ); @@ -148,7 +149,7 @@ describe('PaginatedCatalogTable', () => { }, }} > - { + const data = new Array(100).fill(0).map((_, index) => { + const name = `component-${index}`; + return { + entity: { + apiVersion: '1', + kind: 'component', + metadata: { + name, + }, + }, + resolved: { + name, + entityRef: 'component:default/component', + }, + } as CatalogTableRow; + }); + + const columns = [ + { + title: 'Title', + field: 'entity.metadata.name', + searchable: true, + }, + ]; + + const wrapInContext = ( + node: ReactNode, + value?: Partial>, + ) => { + return ( + + {node} + + ); + }; + + it('should display all the items', async () => { + await renderInTestApp( + wrapInContext( + , + { + setOffset: jest.fn(), + limit: Number.MAX_SAFE_INTEGER, + offset: 0, + totalItems: data.length, + }, + ), + ); + + for (const item of data) { + expect(screen.queryByText(item.resolved.name)).toBeInTheDocument(); + } + }); + + it('should display and invoke the next and previous buttons', async () => { + const offsetFn = jest.fn(); + + await renderInTestApp( + wrapInContext( + , + { setOffset: offsetFn, limit: 10, totalItems: data.length, offset: 0 }, + ), + ); + + expect(offsetFn).toHaveBeenNthCalledWith(1, 0); + const nextButton = screen.queryAllByRole('button', { + name: 'Next Page', + })[0]; + expect(nextButton).toBeEnabled(); + + fireEvent.click(nextButton); + expect(offsetFn).toHaveBeenNthCalledWith(2, 10); + + const prevButton = screen.queryAllByRole('button', { + name: 'Previous Page', + })[0]; + expect(prevButton).toBeEnabled(); + + fireEvent.click(prevButton); + expect(offsetFn).toHaveBeenNthCalledWith(3, 0); + }); +}); diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx new file mode 100644 index 0000000000..1528fb59f9 --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -0,0 +1,73 @@ +/* + * 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 React, { useEffect } from 'react'; + +import { Table, TableProps } from '@backstage/core-components'; +import { CatalogTableRow } from './types'; +import { + EntityTextFilter, + useEntityList, +} from '@backstage/plugin-catalog-react'; + +/** + * @internal + */ +export function OffsetPaginatedCatalogTable( + props: TableProps, +) { + const { columns, data } = props; + const { updateFilters, setLimit, setOffset, limit, totalItems, offset } = + useEntityList(); + const [page, setPage] = React.useState( + offset && limit ? Math.floor(offset / limit) : 0, + ); + + useEffect(() => { + if (totalItems && page * limit >= totalItems) { + setOffset!(Math.max(0, totalItems - limit)); + } else { + setOffset!(Math.max(0, page * limit)); + } + }, [setOffset, page, limit, totalItems]); + + return ( + + updateFilters({ + text: searchText ? new EntityTextFilter(searchText) : undefined, + }) + } + page={page} + onPageChange={newPage => { + setPage(newPage); + }} + onRowsPerPageChange={pageSize => { + setLimit(pageSize); + }} + totalCount={totalItems} + localization={{ pagination: { labelDisplayedRows: '' } }} + /> + ); +} diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 28d63f63d7..40410f3652 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-config-schema +## 0.1.59-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.1.59-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.1.58 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 7fd576a85f..9cd4ed9ece 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.58", + "version": "0.1.59-next.1", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", @@ -54,7 +54,7 @@ "@backstage/dev-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0" + "@testing-library/react": "^16.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 2774ae1b34..0f46a812d8 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-devtools-backend +## 0.4.0-next.2 + +### Patch Changes + +- 5d1670f: Update README installation instructions +- 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/config-loader@1.9.1-next.0 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + ## 0.4.0-next.0 ### Minor Changes diff --git a/plugins/devtools-backend/README.md b/plugins/devtools-backend/README.md index bf0eb64a98..d9ffb3e076 100644 --- a/plugins/devtools-backend/README.md +++ b/plugins/devtools-backend/README.md @@ -4,7 +4,30 @@ Welcome to the DevTools backend plugin! This plugin provides data for the [DevTo ## Setup -Here's how to get the DevTools Backend up and running: +Here's how to get the DevTools Backend up and running in the new backend system: + +1. First we need to add the `@backstage/plugin-devtools-backend` package to your backend: + + ```sh + # From the Backstage root directory + yarn --cwd packages/backend add @backstage/plugin-devtools-backend + ``` + +2. Then add the plugin to your backend index file: + + ```ts + // In packages/backend/src/index.ts + const backend = createBackend(); + // ... other feature additions + backend.add(import('@backstage/plugin-devtools-backend')); + ``` + +3. Now run `yarn start-backend` from the repo root +4. Finally open `http://localhost:7007/api/devtools/health` in a browser and it should return `{"status":"ok"}` + +## Old Backend System + +If you are still on the old backend system, please consider migrating to the new backend system. But to install this plugin in an old backend, see below. 1. First we need to add the `@backstage/plugin-devtools-backend` package to your backend: @@ -50,24 +73,6 @@ Here's how to get the DevTools Backend up and running: 4. Now run `yarn start-backend` from the repo root 5. Finally open `http://localhost:7007/api/devtools/health` in a browser and it should return `{"status":"ok"}` -### New Backend System - -The DevTools backend plugin has support for the [new backend system](https://backstage.io/docs/backend-system/), here's how you can set that up: - -In your `packages/backend/src/index.ts` make the following changes: - -```diff - import { createBackend } from '@backstage/backend-defaults'; - - const backend = createBackend(); - - // ... other feature additions - -+ backend.add(import('@backstage/plugin-devtools-backend')); - - backend.start(); -``` - ## Links - [Frontend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/devtools) diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index cfe0e1d1f5..ff6447b52e 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.4.0-next.0", + "version": "0.4.0-next.2", "backstage": { "role": "backend-plugin", "pluginId": "devtools", @@ -68,7 +68,7 @@ "@types/ping": "^0.4.1", "@types/supertest": "^2.0.8", "@types/yarnpkg__lockfile": "^1.1.4", - "supertest": "^6.2.4" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 0d9dfcc1b2..b4d8296ff3 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-devtools +## 0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-react@0.4.26-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-react@0.4.25 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 22c0b3eddb..9e4c0caab6 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.18-next.0", + "version": "0.1.18-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 52cf45ae2c..ff9247c2ce 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.4.2-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 2354c1fb79..d109490dce 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.2-next.0", + "version": "0.4.2-next.2", "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 ad86906d1c..4a28829cdf 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 11202a486e..91cd11d286 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.11-next.0", + "version": "0.2.11-next.2", "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 bd3a5dd34c..8e93007c7d 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 28a917c763..423fbde187 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.11-next.0", + "version": "0.2.11-next.2", "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 b2b9aa139f..30381d4f72 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 7bd829e02e..6f1bc451d9 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.11-next.0", + "version": "0.2.11-next.2", "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 9ad3204050..ee074ad911 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-events-backend-module-github +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 3c08b10c89..c830a16f4c 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.2.11-next.0", + "version": "0.2.11-next.2", "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 c4faca4da4..447ab1a602 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 5d287fb864..a16cee20d0 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.2.11-next.0", + "version": "0.2.11-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index b9c612b329..a8fbcbaabf 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.1.35-next.0 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index aae188ef7a..f476e79c95 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.35-next.0", + "version": "0.1.35-next.2", "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 f8fbf82dc7..c61aab85d1 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-events-backend +## 0.3.12-next.2 + +### Patch Changes + +- 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/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.2 + +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.4.0-next.1 + ## 0.3.12-next.0 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index b1b586ecfa..84ff688b16 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.12-next.0", + "version": "0.3.12-next.2", "backstage": { "role": "backend-plugin", "pluginId": "events", @@ -65,7 +65,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-events-backend-test-utils": "workspace:^", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 3dfcf3c9e9..c97ea07f9d 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-events-node +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + +## 0.4.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + ## 0.4.0-next.0 ### Minor Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 435f2f6667..7eb4a38bd4 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.0-next.0", + "version": "0.4.0-next.2", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index ea9a0bca3a..bb5e67bd07 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,25 @@ # @internal/plugin-todo-list-backend +## 1.0.31-next.2 + +### Patch Changes + +- 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/errors@1.2.4 + +## 1.0.31-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/errors@1.2.4 + ## 1.0.31-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index dd6f2f74ff..9960a56bd1 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.31-next.0", + "version": "1.0.31-next.2", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", @@ -54,6 +54,6 @@ "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", - "supertest": "^6.1.6" + "supertest": "^7.0.0" } } diff --git a/plugins/example-todo-list-backend/src/plugin.ts b/plugins/example-todo-list-backend/src/plugin.ts index 1c7bba929a..d05fa54c68 100644 --- a/plugins/example-todo-list-backend/src/plugin.ts +++ b/plugins/example-todo-list-backend/src/plugin.ts @@ -26,7 +26,7 @@ import { createRouter } from './service/router'; * @public */ export const exampleTodoListPlugin = createBackendPlugin({ - pluginId: 'exampleTodoList', + pluginId: 'todolist', register(env) { env.registerInit({ deps: { diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index c5ccfbcd54..fcd7462dff 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,21 @@ # @internal/plugin-todo-list +## 1.0.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + +## 1.0.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + ## 1.0.30 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index eb13f52c6b..7895c5935d 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.30", + "version": "1.0.31-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", @@ -49,7 +49,7 @@ "@backstage/cli": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@types/react": "^16.13.1 || ^17.0.0" }, "peerDependencies": { diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 87ff61daf0..487290d0bf 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-home-react +## 0.1.17-next.1 + +### Patch Changes + +- fa9d8da: Updated dependency `@rjsf/utils` to `5.20.1`. + Updated dependency `@rjsf/core` to `5.20.1`. + Updated dependency `@rjsf/material-ui` to `5.20.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.20.1`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + ## 0.1.16 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index e720402766..48d5122ab3 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.16", + "version": "0.1.17-next.1", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", @@ -46,7 +46,7 @@ "@backstage/core-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@rjsf/utils": "5.18.5", + "@rjsf/utils": "5.20.1", "@types/react": "^16.13.1 || ^17.0.0" }, "devDependencies": { diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 4d412eae76..ceb22dfe21 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-home +## 0.7.10-next.2 + +### Patch Changes + +- c891b69: Add `FavoriteToggle` in `core-components` to standardise favorite marking +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- fa9d8da: Updated dependency `@rjsf/utils` to `5.20.1`. + Updated dependency `@rjsf/core` to `5.20.1`. + Updated dependency `@rjsf/material-ui` to `5.20.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.20.1`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-app-api@1.14.3-next.0 + - @backstage/catalog-client@1.7.0-next.1 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-home-react@0.1.17-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + +## 0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/catalog-client@1.6.7-next.0 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @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/theme@0.5.6 + - @backstage/plugin-home-react@0.1.17-next.0 + ## 0.7.10-next.0 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index c7ef7b3862..8f0c567bf9 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.10-next.0", + "version": "0.7.10-next.2", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", @@ -70,10 +70,10 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@rjsf/core": "5.18.5", - "@rjsf/material-ui": "5.18.5", - "@rjsf/utils": "5.18.5", - "@rjsf/validator-ajv8": "5.18.5", + "@rjsf/core": "5.20.1", + "@rjsf/material-ui": "5.20.1", + "@rjsf/utils": "5.20.1", + "@rjsf/validator-ajv8": "5.20.1", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", "lodash": "^4.17.21", "luxon": "^3.4.3", @@ -88,7 +88,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "@types/react-grid-layout": "^1.3.2" }, diff --git a/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx index 579116b9a9..8081e71407 100644 --- a/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx +++ b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx @@ -17,14 +17,12 @@ import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { entityRouteParams } from '@backstage/plugin-catalog-react'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; -import Tooltip from '@material-ui/core/Tooltip'; -import IconButton from '@material-ui/core/IconButton'; import ListItemText from '@material-ui/core/ListItemText'; import React from 'react'; import { Link } from 'react-router-dom'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { useRouteRef } from '@backstage/core-plugin-api'; -import StarIcon from '@material-ui/icons/Star'; +import { FavoriteToggle } from '@backstage/core-components'; type EntityListItemProps = { entity: Entity; @@ -40,15 +38,12 @@ export const StarredEntityListItem = ({ return ( - - onToggleStarredEntity(entity)} - > - - - + onToggleStarredEntity(entity)} + /> diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 3e81a64b08..98c0bd23ec 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-kubernetes-backend +## 0.18.6-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/catalog-client@1.7.0-next.1 + - @backstage/plugin-kubernetes-node@0.1.19-next.2 + - @backstage/plugin-permission-node@0.8.3-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-permission-common@0.8.1 + +## 0.18.6-next.1 + +### Patch Changes + +- ca96b66: Skip start without proper config +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-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/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-node@0.1.19-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.3-next.1 + ## 0.18.6-next.0 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 1401870767..6e7bf85660 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.18.6-next.0", + "version": "0.18.6-next.2", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", @@ -106,7 +106,7 @@ "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^", "@types/aws4": "^1.5.1", "msw": "^1.0.0", - "supertest": "^6.1.3", + "supertest": "^7.0.0", "ws": "^8.13.0" }, "configSchema": "config.d.ts" diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 8fedd3d637..1451aa49d7 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.15-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/plugin-kubernetes-react@0.4.3-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + ## 0.0.15-next.0 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 65e475675b..be058221d0 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.15-next.0", + "version": "0.0.15-next.2", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", @@ -64,7 +64,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@types/node": "^16.11.26" }, "peerDependencies": { diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 7b687e0fa7..7ebeb9a6e5 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-kubernetes-node +## 0.1.19-next.2 + +### Patch Changes + +- c2b63ab: Updated dependency `supertest` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.1.19-next.0 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 2c998e9802..39d80b5152 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.19-next.0", + "version": "0.1.19-next.2", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", @@ -54,6 +54,6 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", "msw": "^1.3.1", - "supertest": "^6.1.3" + "supertest": "^7.0.0" } } diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index d76b454a4b..18de232cc4 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-kubernetes-react +## 0.4.3-next.1 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + ## 0.4.2 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 936231da7c..4883e5dbb6 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-react", - "version": "0.4.2", + "version": "0.4.3-next.1", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", @@ -69,7 +69,7 @@ "@backstage/core-app-api": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "jest-websocket-mock": "^2.5.0", "msw": "^1.3.1" }, diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 4ff750f743..7e25fc2d40 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-kubernetes +## 0.11.14-next.2 + +### Patch Changes + +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/plugin-catalog-react@1.13.0-next.2 + - @backstage/core-compat-api@0.3.0-next.2 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.11.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-compat-api@0.3.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/plugin-catalog-react@1.12.4-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + - @backstage/plugin-kubernetes-react@0.4.3-next.0 + ## 0.11.14-next.0 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 238155d940..789c3375ce 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.11.14-next.0", + "version": "0.11.14-next.2", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", @@ -86,7 +86,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0" + "@testing-library/react": "^16.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 33d1df94c1..cb45a80276 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.0-next.2 + +### Patch Changes + +- 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/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.2 + +## 0.3.0-next.1 + +### Patch Changes + +- 5edd344: Refactor to use injected catalog client in the new backend system +- 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/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + ## 0.3.0-next.0 ### Minor Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 20e076cdd2..bc5d544d90 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.0-next.0", + "version": "0.3.0-next.2", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -42,6 +42,7 @@ "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration-aws-node": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/module.ts b/plugins/notifications-backend-module-email/src/module.ts index 6b74bf70e7..c83d27b6a3 100644 --- a/plugins/notifications-backend-module-email/src/module.ts +++ b/plugins/notifications-backend-module-email/src/module.ts @@ -17,7 +17,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { CatalogClient } from '@backstage/catalog-client'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { NotificationsEmailProcessor } from './processor'; import { @@ -46,21 +46,17 @@ export const notificationsModuleEmail = createBackendModule({ deps: { config: coreServices.rootConfig, notifications: notificationsProcessingExtensionPoint, - discovery: coreServices.discovery, logger: coreServices.logger, auth: coreServices.auth, cache: coreServices.cache, + catalog: catalogServiceRef, }, - async init({ config, notifications, discovery, logger, auth, cache }) { - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); - + async init({ config, notifications, logger, auth, cache, catalog }) { notifications.addProcessor( new NotificationsEmailProcessor( logger, config, - catalogClient, + catalog, auth, cache, templateRenderer, diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index a876a548d2..9337915ecc 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -24,10 +24,7 @@ import { } from '@backstage/backend-plugin-api'; import { Config, readDurationFromConfig } from '@backstage/config'; import { durationToMilliseconds } from '@backstage/types'; -import { - CATALOG_FILTER_EXISTS, - CatalogClient, -} from '@backstage/catalog-client'; +import { CATALOG_FILTER_EXISTS, CatalogApi } from '@backstage/catalog-client'; import { getProcessorFiltersFromConfig, Notification, @@ -63,7 +60,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { constructor( private readonly logger: LoggerService, private readonly config: Config, - private readonly catalog: CatalogClient, + private readonly catalog: CatalogApi, private readonly auth: AuthService, private readonly cache?: CacheService, private readonly templateRenderer?: NotificationTemplateRenderer, diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 081048900f..5ea979f8fc 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-notifications-backend +## 0.4.0-next.2 + +### Patch Changes + +- 4a53dd0: Implement icon in backend and show icon in table if available. +- 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/catalog-client@1.7.0-next.1 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-events-node@0.4.0-next.2 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.2 + - @backstage/plugin-signals-node@0.1.11-next.2 + +## 0.4.0-next.1 + +### Patch Changes + +- f195972: Validate notification link when new notification is created +- 5edd344: Refactor to use injected catalog client in the new backend system +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/plugin-auth-node@0.5.2-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/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-events-node@0.4.0-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.6-next.1 + - @backstage/plugin-signals-node@0.1.11-next.1 + ## 0.4.0-next.0 ### Minor Changes diff --git a/plugins/notifications-backend/migrations/20240902_addIcon.js b/plugins/notifications-backend/migrations/20240902_addIcon.js new file mode 100644 index 0000000000..a5327f8d73 --- /dev/null +++ b/plugins/notifications-backend/migrations/20240902_addIcon.js @@ -0,0 +1,33 @@ +/* + * 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. + */ + +exports.up = async function up(knex) { + await knex.schema.alterTable('notification', table => { + table.string('icon', 255).nullable(); + }); + await knex.schema.alterTable('broadcast', table => { + table.string('icon', 255).nullable(); + }); +}; + +exports.down = async function down(knex) { + await knex.schema.alterTable('notification', table => { + table.dropColumn('icon'); + }); + await knex.schema.alterTable('broadcast', table => { + table.dropColumn('icon'); + }); +}; diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index a0273d628b..d593d6865e 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.4.0-next.0", + "version": "0.4.0-next.2", "backstage": { "role": "backend-plugin", "pluginId": "notifications", @@ -45,6 +45,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", @@ -68,6 +69,6 @@ "@types/express": "^4.17.6", "@types/supertest": "^2.0.8", "msw": "^1.0.0", - "supertest": "^6.2.4" + "supertest": "^7.0.0" } } diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index d4e678c8f1..0c7193c1fc 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -68,6 +68,7 @@ const testNotification1: Notification = { topic: 'efgh-topic', link: '/catalog', severity: 'critical', + icon: 'docs', }, }; const testNotification2: Notification = { @@ -183,6 +184,7 @@ describe.each(databases.eachSupportedId())( expect(notification?.payload?.topic).toBe('efgh-topic'); expect(notification?.payload?.link).toBe('/catalog'); expect(notification?.payload?.severity).toBe('critical'); + expect(notification?.payload?.icon).toBe('docs'); }); }); diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index b23f5254f4..40a76c7486 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -41,6 +41,7 @@ const NOTIFICATION_COLUMNS = [ 'origin', 'scope', 'topic', + 'icon', 'created', 'updated', 'user', @@ -119,6 +120,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { description: notification.payload?.description, severity: normalizeSeverity(notification.payload?.severity), scope: notification.payload?.scope, + icon: notification.payload.icon, saved: notification.saved, read: notification.read, }; @@ -134,6 +136,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { title: notification.payload?.title, description: notification.payload?.description, severity: normalizeSeverity(notification.payload?.severity), + icon: notification.payload.icon, scope: notification.payload?.scope, }; }; diff --git a/plugins/notifications-backend/src/plugin.ts b/plugins/notifications-backend/src/plugin.ts index 288a0f8507..84a200bae1 100644 --- a/plugins/notifications-backend/src/plugin.ts +++ b/plugins/notifications-backend/src/plugin.ts @@ -25,6 +25,7 @@ import { notificationsProcessingExtensionPoint, NotificationsProcessingExtensionPoint, } from '@backstage/plugin-notifications-node'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; class NotificationsProcessingExtensionPointImpl implements NotificationsProcessingExtensionPoint @@ -65,9 +66,9 @@ export const notificationsPlugin = createBackendPlugin({ httpRouter: coreServices.httpRouter, logger: coreServices.logger, database: coreServices.database, - discovery: coreServices.discovery, signals: signalsServiceRef, config: coreServices.rootConfig, + catalog: catalogServiceRef, }, async init({ auth, @@ -76,9 +77,9 @@ export const notificationsPlugin = createBackendPlugin({ httpRouter, logger, database, - discovery, signals, config, + catalog, }) { httpRouter.use( await createRouter({ @@ -88,7 +89,7 @@ export const notificationsPlugin = createBackendPlugin({ logger, config, database, - discovery, + catalog, signals, processors: processingExtensions.processors, }), diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index a3a66636ca..b90ebcaf50 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { DatabaseManager, PluginDatabaseManager, @@ -25,6 +26,7 @@ import { ConfigReader } from '@backstage/config'; import { SignalsService } from '@backstage/plugin-signals-node'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; import { NotificationSendOptions } from '@backstage/plugin-notifications-node'; +import { CatalogClient } from '@backstage/catalog-client'; function createDatabase(): PluginDatabaseManager { return DatabaseManager.fromConfig( @@ -46,7 +48,6 @@ describe('createRouter', () => { publish: jest.fn(), }; - const discovery = mockServices.discovery(); const userInfo = mockServices.userInfo(); const httpAuth = mockServices.httpAuth({ defaultCredentials: mockCredentials.service(), @@ -55,17 +56,20 @@ describe('createRouter', () => { const config = mockServices.rootConfig({ data: { app: { baseUrl: 'http://localhost' } }, }); + const catalog = new CatalogClient({ + discoveryApi: mockServices.discovery.mock(), + }); beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), database: createDatabase(), - discovery, signals: signalService, userInfo, config, httpAuth, auth, + catalog, }); app = express().use(router); }); diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 95b9467f4a..fb5aacb5f4 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { errorHandler, PluginDatabaseManager } from '@backstage/backend-common'; import express, { Request } from 'express'; import Router from 'express-promise-router'; @@ -22,7 +23,7 @@ import { NotificationGetOptions, } from '../database'; import { v4 as uuid } from 'uuid'; -import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; +import { CatalogApi } from '@backstage/catalog-client'; import { NotificationProcessor, NotificationSendOptions, @@ -30,7 +31,6 @@ import { import { InputError } from '@backstage/errors'; import { AuthService, - DiscoveryService, HttpAuthService, LoggerService, UserInfoService, @@ -53,12 +53,11 @@ export interface RouterOptions { logger: LoggerService; config: Config; database: PluginDatabaseManager; - discovery: DiscoveryService; auth: AuthService; httpAuth: HttpAuthService; userInfo: UserInfoService; signals?: SignalsService; - catalog?: CatalogApi; + catalog: CatalogApi; processors?: NotificationProcessor[]; } @@ -73,14 +72,11 @@ export async function createRouter( auth, httpAuth, userInfo, - discovery, catalog, processors = [], signals, } = options; - const catalogClient = - catalog ?? new CatalogClient({ discoveryApi: discovery }); const store = await DatabaseNotificationsStore.create({ database }); const frontendBaseUrl = config.getString('app.baseUrl'); @@ -474,7 +470,7 @@ export async function createRouter( users = await getUsersForEntityRef( entityRef, recipients.excludeEntityRef ?? [], - { auth, catalogClient }, + { auth, catalogClient: catalog }, ); } catch (e) { logger.error(`Failed to resolve notification receivers: ${e}`); diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index e1d48a7d88..7282fa0eed 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-notifications-node +## 0.2.6-next.2 + +### Patch Changes + +- 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/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.11-next.2 + +## 0.2.6-next.1 + +### Patch Changes + +- 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/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.11-next.1 + ## 0.2.6-next.0 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index af3fdcde9b..36db4a0b8b 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.6-next.0", + "version": "0.2.6-next.2", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 19cd62732a..1d448ef6c0 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-notifications +## 0.3.1-next.2 + +### Patch Changes + +- 653f60b: Severity filter label newly contains "Min severity" to better describe range instead of exact value. +- 4a53dd0: Implement icon in backend and show icon in table if available. +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/theme@0.5.7-next.0 + - @backstage/plugin-signals-react@0.0.5-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-react@0.0.5-next.0 + ## 0.3.1-next.0 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index ba211d21ea..f4a709554e 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.3.1-next.0", + "version": "0.3.1-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", @@ -62,7 +62,7 @@ "@backstage/plugin-signals": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^15.0.0", + "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, diff --git a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx index 3175af6f6f..2676016695 100644 --- a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx +++ b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx @@ -249,10 +249,12 @@ export const NotificationsFilters = ({ - Severity + + Min severity + onChange({ repository: e.target.value })} - defaultValue="" - /> + it('should omit properties that are no longer pertinent to the current step', async () => { + const manifest: TemplateParameterSchema = { + title: 'Conditional Input Form', + steps: [ + { + title: 'Conditional Input step', + schema: { + type: 'object', + properties: { + moreInfo: { + type: 'boolean', + title: 'More info', + }, + }, + dependencies: { + moreInfo: { + oneOf: [ + { + properties: { + moreInfo: { + const: true, + }, + description: { + type: 'string', + title: 'Description', + }, + }, + required: ['description'], + }, + { + properties: { + moreInfo: { + not: { + const: true, + }, + }, + }, + }, + ], + }, + }, + }, + }, + ], + }; + + const onCreate = jest.fn(); + + const { getByRole, queryByRole } = await renderInTestApp( + + + , ); - const Owner = ({ - onChange, - }: FieldExtensionComponentProps<{ owner: string }, any>) => ( - onChange({ owner: e.target.value })} - defaultValue="" - /> - ); + await act(async () => { + fireEvent.click(getByRole('checkbox', { name: 'More info' })); + fireEvent.change(getByRole('textbox', { name: 'Description' }), { + target: { value: 'My Test Description' }, + }); + fireEvent.click(getByRole('button', { name: 'Review' })); + }); + expect( + getByRole('cell', { name: 'My Test Description' }), + ).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(getByRole('button', { name: 'Back' })); + }); + + await act(async () => { + fireEvent.click(getByRole('checkbox', { name: 'More info' })); + fireEvent.click(getByRole('button', { name: 'Review' })); + }); + + expect( + queryByRole('cell', { name: 'My Test Description' }), + ).not.toBeInTheDocument(); + + await act(async () => { + fireEvent.click(getByRole('button', { name: 'Create' })); + }); + + expect(onCreate).toHaveBeenCalledWith({ moreInfo: false }); + }); + + it('should merge and overwrite nested formData correctly', async () => { const manifest: TemplateParameterSchema = { steps: [ { title: 'Step 1', schema: { properties: { - first: { - type: 'object', - 'ui:field': 'Repo', + foo: { + type: 'string', + title: 'Foo - 1', }, }, }, @@ -209,9 +277,13 @@ describe('Stepper', () => { title: 'Step 2', schema: { properties: { - second: { - type: 'object', - 'ui:field': 'Owner', + foo: { + type: 'string', + title: 'Foo - 2', + }, + bar: { + type: 'string', + title: 'Bar - 2', }, }, }, @@ -220,47 +292,39 @@ describe('Stepper', () => { title: 'React JSON Schema Form Test', }; - const onCreate = jest.fn(async (values: Record) => { - expect(values).toEqual({ - first: { repository: 'Repo' }, - second: { owner: 'Owner' }, - }); - }); + const onCreate = jest.fn(); const { getByRole } = await renderInTestApp( - + , ); - await fireEvent.change(getByRole('textbox', { name: 'repo' }), { - target: { value: 'Repo' }, + await act(async () => { + fireEvent.change(getByRole('textbox', { name: 'Foo - 1' }), { + target: { value: 'value 1' }, + }); + fireEvent.click(getByRole('button', { name: 'Next' })); }); await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Next' })); - }); - - await fireEvent.change(getByRole('textbox', { name: 'owner' }), { - target: { value: 'Owner' }, + fireEvent.change(getByRole('textbox', { name: 'Foo - 2' }), { + target: { value: 'value 2' }, + }); + fireEvent.change(getByRole('textbox', { name: 'Bar - 2' }), { + target: { value: 'value 2' }, + }); + fireEvent.click(getByRole('button', { name: 'Review' })); }); await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Review' })); + fireEvent.click(getByRole('button', { name: 'Create' })); }); - await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Create' })); + expect(onCreate).toHaveBeenCalledWith({ + foo: 'value 2', + bar: 'value 2', }); - - expect(onCreate).toHaveBeenCalled(); }); it('should render custom field extensions properly', async () => { @@ -335,13 +399,11 @@ describe('Stepper', () => { /> , ); - - act(() => { - fireEvent.click(getByRole('button', { name: 'Review' })); + fireEvent.click(getByRole('button', { name: 'Review' })); + await waitFor(() => { + expect(getByRole('progressbar')).toBeInTheDocument(); // Check if progress bar is rendered + expect(getByRole('button', { name: 'Review' })).toBeDisabled(); // Check if the button is disabled }); - - expect(getByRole('progressbar')).toBeInTheDocument(); - expect(getByRole('button', { name: 'Review' })).toBeDisabled(); }); it('should transform default error message', async () => { @@ -381,12 +443,11 @@ describe('Stepper', () => { , ); - await fireEvent.change(getByRole('textbox', { name: 'postcode' }), { - target: { value: 'invalid' }, - }); - await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Review' })); + fireEvent.change(getByRole('textbox', { name: 'postcode' }), { + target: { value: 'invalid' }, + }); + fireEvent.click(getByRole('button', { name: 'Review' })); }); expect(getByText('invalid postcode')).toBeInTheDocument(); @@ -421,12 +482,12 @@ describe('Stepper', () => { , ); - await fireEvent.change(getByRole('textbox', { name: 'postcode' }), { - target: { value: 'invalid' }, - }); - await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Review' })); + fireEvent.change(getByRole('textbox', { name: 'postcode' }), { + target: { value: 'invalid' }, + }); + + fireEvent.click(getByRole('button', { name: 'Review' })); }); expect(getByText('invalid postcode')).toBeInTheDocument(); @@ -494,13 +555,13 @@ describe('Stepper', () => { ); await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Review' })); + fireEvent.click(getByRole('button', { name: 'Review' })); }); expect(getByRole('button', { name: 'Create' })).toBeInTheDocument(); await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Create' })); + fireEvent.click(getByRole('button', { name: 'Create' })); }); // flush promises @@ -539,13 +600,13 @@ describe('Stepper', () => { ); await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Inspect' })); + fireEvent.click(getByRole('button', { name: 'Inspect' })); }); expect(getByRole('button', { name: 'Make' })).toBeInTheDocument(); await act(async () => { - await fireEvent.click(getByRole('button', { name: 'Make' })); + fireEvent.click(getByRole('button', { name: 'Make' })); }); }); diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 3a8858144b..a1b31d0089 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -115,11 +115,16 @@ export const Stepper = (stepperProps: StepperProps) => { const apiHolder = useApiHolder(); const [activeStep, setActiveStep] = useState(0); const [isValidating, setIsValidating] = useState(false); - const [formState, setFormState] = useFormDataFromQuery(props.initialState); + const [initialState] = useFormDataFromQuery(props.initialState); + const [formState, setFormState] = useState<{ + [step: string]: Record; + }>(); const [errors, setErrors] = useState(); const styles = useStyles(); + const makeStepKey = (step: string | number) => `step-${step}`; + const backLabel = presentation?.buttonLabels?.backButtonText ?? backButtonText; const createLabel = @@ -155,16 +160,15 @@ export const Stepper = (stepperProps: StepperProps) => { }; const handleChange = useCallback( - (e: IChangeEvent) => - setFormState(current => ({ ...current, ...e.formData })), - [setFormState], + (e: IChangeEvent) => { + setFormState(current => ({ + ...current, + [makeStepKey(activeStep)]: e.formData, + })); + }, + [setFormState, activeStep], ); - const handleCreate = useCallback(() => { - props.onCreate(formState); - analytics.captureEvent('click', `${createLabel}`); - }, [props, formState, analytics, createLabel]); - const currentStep = useTransformSchemaToProps(steps[activeStep], { layouts }); const handleNext = async ({ @@ -191,7 +195,10 @@ export const Stepper = (stepperProps: StepperProps) => { return stepNum; }); } - setFormState(current => ({ ...current, ...formData })); + setFormState(current => ({ + ...current, + [makeStepKey(activeStep)]: formData, + })); }; const { @@ -202,6 +209,24 @@ export const Stepper = (stepperProps: StepperProps) => { const mergedUiSchema = merge({}, propUiSchema, currentStep?.uiSchema); + const mergedState = useMemo(() => { + if (!formState) { + return initialState; + } + const { [makeStepKey(activeStep)]: activeState, ...historicalState } = + formState; + const chronologicalState = { + ...historicalState, + [makeStepKey(activeStep)]: activeState, + }; + return merge({}, ...Object.values(chronologicalState)); + }, [formState, activeStep, initialState]); + + const handleCreate = useCallback(() => { + props.onCreate(mergedState); + analytics.captureEvent('click', `${createLabel}`); + }, [props, mergedState, analytics, createLabel]); + return ( <> {isValidating && } @@ -237,8 +262,8 @@ export const Stepper = (stepperProps: StepperProps) => {
{ ReviewStepComponent ? ( {}} steps={steps} @@ -282,7 +307,7 @@ export const Stepper = (stepperProps: StepperProps) => { /> ) : ( <> - +
@@ -206,7 +209,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { export function TemplateEditorFormDirectoryEditorDryRun( props: Pick< TemplateEditorFormProps, - 'setErrorText' | 'fieldExtensions' | 'layouts' + 'setErrorText' | 'fieldExtensions' | 'layouts' | 'formProps' >, ) { const { setErrorText, fieldExtensions = [], layouts } = props; @@ -245,6 +248,7 @@ export function TemplateEditorFormDirectoryEditorDryRun( setErrorText={setErrorText} content={content} layouts={layouts} + formProps={props.formProps} /> ); } diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx index 6e7c921e91..03d656c98d 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.tsx @@ -24,6 +24,7 @@ import { TemplateEditor } from './TemplateEditor'; import { TemplateFormPreviewer } from './TemplateFormPreviewer'; import { FieldExtensionOptions, + FormProps, type LayoutOptions, } from '@backstage/plugin-scaffolder-react'; import { TemplateEditorIntro } from './TemplateEditorIntro'; @@ -54,6 +55,7 @@ interface TemplateEditorPageProps { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; layouts?: LayoutOptions[]; + formProps?: FormProps; } export function TemplateEditorPage(props: TemplateEditorPageProps) { @@ -79,6 +81,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { fieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} layouts={props.layouts} + formProps={props.formProps} /> ); } else if (selection?.type === 'form') { @@ -88,6 +91,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { customFieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} layouts={props.layouts} + formProps={props.formProps} /> ); } else if (selection?.type === 'field-explorer') { diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx index 07f2fd7f07..4f79b7f5c2 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -33,6 +33,7 @@ import yaml from 'yaml'; import { LayoutOptions, FieldExtensionOptions, + FormProps, } from '@backstage/plugin-scaffolder-react'; import { TemplateEditorForm } from './TemplateEditorForm'; import { TemplateEditorTextArea } from './TemplateEditorTextArea'; @@ -83,42 +84,54 @@ type TemplateOption = { value: Entity; }; -const useStyles = makeStyles(theme => ({ - root: { - gridArea: 'pageContent', - display: 'grid', - gridTemplateAreas: ` +/** @public */ +export type ScaffolderTemplateFormPreviewerClassKey = + | 'root' + | 'controls' + | 'textArea' + | 'preview'; + +const useStyles = makeStyles( + theme => ({ + root: { + gridArea: 'pageContent', + display: 'grid', + gridTemplateAreas: ` "controls controls" "textArea preview" `, - gridTemplateRows: 'auto 1fr', - gridTemplateColumns: '1fr 1fr', - }, - controls: { - gridArea: 'controls', - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - margin: theme.spacing(1), - }, - textArea: { - gridArea: 'textArea', - }, - preview: { - gridArea: 'preview', - }, -})); + gridTemplateRows: 'auto 1fr', + gridTemplateColumns: '1fr 1fr', + }, + controls: { + gridArea: 'controls', + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + margin: theme.spacing(1), + }, + textArea: { + gridArea: 'textArea', + }, + preview: { + gridArea: 'preview', + }, + }), + { name: 'ScaffolderTemplateFormPreviewer' }, +); export const TemplateFormPreviewer = ({ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, customFieldExtensions = [], onClose, layouts = [], + formProps, }: { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; onClose?: () => void; layouts?: LayoutOptions[]; + formProps?: FormProps; }) => { const classes = useStyles(); const { t } = useTranslationRef(scaffolderTranslationRef); @@ -213,6 +226,7 @@ export const TemplateFormPreviewer = ({ fieldExtensions={customFieldExtensions} setErrorText={setErrorText} layouts={layouts} + formProps={formProps} /> diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/index.ts b/plugins/scaffolder/src/next/TemplateEditorPage/index.ts index 7ec6bddb64..7f70c55ed9 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/index.ts +++ b/plugins/scaffolder/src/next/TemplateEditorPage/index.ts @@ -15,3 +15,6 @@ */ export { TemplateEditorPage } from './TemplateEditorPage'; +export type { ScaffolderCustomFieldExplorerClassKey } from './CustomFieldExplorer'; +export type { ScaffolderTemplateEditorClassKey } from './TemplateEditor'; +export type { ScaffolderTemplateFormPreviewerClassKey } from './TemplateFormPreviewer'; diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx index 4042506d1e..4c95027af7 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateListPage.tsx @@ -177,7 +177,7 @@ export const TemplateListPage = (props: TemplateListPageProps) => { - + + >; +}; + +declare module '@backstage/theme' { + interface OverrideComponentNameToClassKeys + extends ScaffolderReactComponentsNameToClassKey {} +} diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts index d97c3f6f05..3518ed2c53 100644 --- a/plugins/scaffolder/src/translation.ts +++ b/plugins/scaffolder/src/translation.ts @@ -261,7 +261,6 @@ export const scaffolderTranslationRef = createTranslationRef({ otherTitle: 'Other Templates', }, contentHeader: { - title: 'Available Templates', registerExistingButtonTitle: 'Register Existing Component', supportButtonTitle: 'Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).', diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index da431dadcb..a39f07257c 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-search-backend-module-catalog +## 0.2.2-next.2 + +### Patch Changes + +- 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/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.2-next.1 + +### Patch Changes + +- 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/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index ea216c5033..f3cd8ff7b0 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.2.2-next.0", + "version": "0.2.2-next.2", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 65cf4bb263..9683d2a1a4 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 1.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + ## 1.5.6-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 969152c536..4b1d173048 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.5.6-next.0", + "version": "1.5.6-next.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-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index 05c68e9d00..9629ff0639 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-search-backend-module-explore +## 0.2.2-next.2 + +### Patch Changes + +- 276f433: Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.5`. +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 7aa0b449ae..aef6c7fd61 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.2.2-next.0", + "version": "0.2.2-next.2", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module", @@ -48,7 +48,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage-community/plugin-explore-common": "^0.0.4", + "@backstage-community/plugin-explore-common": "^0.0.5", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index cac5550c8a..9d6da34de0 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 0.5.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + ## 0.5.35-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 01b20fb454..7147f83840 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.35-next.0", + "version": "0.5.35-next.2", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index 92c8c81331..aa7c0ce96c 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.2 + - @backstage/backend-plugin-api@1.0.0-next.2 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 0.3.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.25.0-next.1 + - @backstage/backend-plugin-api@0.9.0-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + ## 0.3.0-next.0 ### Minor Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index 84363f010b..280217b8e3 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.0-next.0", + "version": "0.3.0-next.2", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 47e94e2937..16b2887cfc 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.2.2-next.2 + +### Patch Changes + +- 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/plugin-techdocs-node@1.12.11-next.2 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.2 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.2 + - @backstage/plugin-search-common@1.2.14 + +## 0.2.2-next.1 + +### Patch Changes + +- 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/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-node@1.12.7-next.1 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-node@1.3.2-next.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-techdocs-node@1.12.11-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 9d5f3b238a..6d89f7601a 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.2.2-next.0", + "version": "0.2.2-next.2", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index bcbee26057..ecc3945d55 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-search-backend-node +## 1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-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 + - @backstage/plugin-search-common@1.2.14 + +## 1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.5.0-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-search-common@1.2.14 + ## 1.3.2-next.0 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index a900da8917..50b02fd965 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.2-next.0", + "version": "1.3.2-next.2", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 4581b5ac91..e229f8be94 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-search-backend +## 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 diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 2c635fa4fe..05dc6ba72e 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.17-next.0", + "version": "1.5.17-next.2", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", @@ -80,7 +80,7 @@ "@backstage/cli": "workspace:^", "@backstage/repo-tools": "workspace:^", "@types/supertest": "^2.0.8", - "supertest": "^6.1.3" + "supertest": "^7.0.0" }, "configSchema": "config.d.ts" } diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index b86490a172..97890ec0b4 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-search-react +## 1.8.0-next.2 + +### Patch Changes + +- dbbd93e: Internal update to match recent React types +- 836127c: Updated dependency `@testing-library/react` to `^16.0.0`. +- Updated dependencies + - @backstage/core-components@0.14.11-next.1 + - @backstage/core-plugin-api@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.8.0-next.2 + - @backstage/theme@0.5.7-next.0 + - @backstage/version-bridge@1.0.9-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.14 + +## 1.8.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.8.0-next.1 + - @backstage/core-components@0.14.11-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.14 + ## 1.8.0-next.0 ### Minor Changes diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index d151ef1187..34d79fb16f 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -132,21 +132,12 @@ export type SearchAutocompleteProps