diff --git a/.changeset/big-eagles-grab.md b/.changeset/big-eagles-grab.md
new file mode 100644
index 0000000000..9ece8f64df
--- /dev/null
+++ b/.changeset/big-eagles-grab.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-github': patch
+---
+
+Added examples for github:environment:create action and improve its test cases
diff --git a/.changeset/breezy-jeans-tie.md b/.changeset/breezy-jeans-tie.md
new file mode 100644
index 0000000000..4601389a4c
--- /dev/null
+++ b/.changeset/breezy-jeans-tie.md
@@ -0,0 +1,18 @@
+---
+'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch
+'@backstage/plugin-auth-backend-module-vmware-cloud-provider': patch
+'@backstage/plugin-auth-backend-module-atlassian-provider': patch
+'@backstage/plugin-auth-backend-module-bitbucket-provider': patch
+'@backstage/plugin-auth-backend-module-microsoft-provider': patch
+'@backstage/plugin-auth-backend-module-onelogin-provider': patch
+'@backstage/plugin-auth-backend-module-aws-alb-provider': patch
+'@backstage/plugin-auth-backend-module-gcp-iap-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-oauth2-provider': patch
+'@backstage/plugin-auth-backend-module-oidc-provider': patch
+'@backstage/plugin-auth-backend-module-okta-provider': patch
+---
+
+Add `signIn` to authentication provider configuration schema
diff --git a/.changeset/chilly-trains-sleep.md b/.changeset/chilly-trains-sleep.md
new file mode 100644
index 0000000000..cde7600c8b
--- /dev/null
+++ b/.changeset/chilly-trains-sleep.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-events-backend-module-aws-sqs': patch
+'@backstage/plugin-catalog-backend-module-aws': patch
+'@backstage/backend-common': patch
+---
+
+Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail.
diff --git a/.changeset/cuddly-zebras-crash.md b/.changeset/cuddly-zebras-crash.md
new file mode 100644
index 0000000000..473ac4053f
--- /dev/null
+++ b/.changeset/cuddly-zebras-crash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Use ES2022 in CLI bundler
diff --git a/.changeset/dry-squids-tap.md b/.changeset/dry-squids-tap.md
index 613ec4b7cb..0a95332eed 100644
--- a/.changeset/dry-squids-tap.md
+++ b/.changeset/dry-squids-tap.md
@@ -11,13 +11,9 @@ This allows the creation of extension instances with the following pattern:
const EntityCardBlueprint = createExtensionBlueprint({
kind: 'entity-card',
attachTo: { id: 'test', input: 'default' },
- output: {
- element: coreExtensionData.reactElement,
- },
+ output: [coreExtensionData.reactElement],
factory(params: { text: string }) {
- return {
- element:
{params.text}
,
- };
+ return [coreExtensionData.reactElement({params.text}
)];
},
});
diff --git a/.changeset/dull-ghosts-double.md b/.changeset/dull-ghosts-double.md
new file mode 100644
index 0000000000..0bc5a09791
--- /dev/null
+++ b/.changeset/dull-ghosts-double.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-catalog-react': patch
+'@backstage/plugin-search-react': patch
+'@backstage/plugin-home': patch
+---
+
+Updated alpha definitions of extension data references.
diff --git a/.changeset/eighty-mirrors-flow.md b/.changeset/eighty-mirrors-flow.md
new file mode 100644
index 0000000000..57e978a78c
--- /dev/null
+++ b/.changeset/eighty-mirrors-flow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component
diff --git a/.changeset/few-wasps-hug.md b/.changeset/few-wasps-hug.md
new file mode 100644
index 0000000000..66cf34fc0b
--- /dev/null
+++ b/.changeset/few-wasps-hug.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Correct `EntityDisplayName`'s icon alignment with the text.
diff --git a/.changeset/flat-plums-grow.md b/.changeset/flat-plums-grow.md
new file mode 100644
index 0000000000..7057216786
--- /dev/null
+++ b/.changeset/flat-plums-grow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-react': patch
+---
+
+Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps`
diff --git a/.changeset/forty-ties-agree.md b/.changeset/forty-ties-agree.md
new file mode 100644
index 0000000000..caa45841a2
--- /dev/null
+++ b/.changeset/forty-ties-agree.md
@@ -0,0 +1,18 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons.
+
+```tsx
+import { IconBundleBlueprint } from '@backstage/frontend-plugin-api';
+
+const exampleIconBundle = IconBundleBlueprint.make({
+ name: 'example-bundle',
+ params: {
+ icons: {
+ user: MyOwnUserIcon,
+ },
+ },
+});
+```
diff --git a/.changeset/forty-ties-disagree.md b/.changeset/forty-ties-disagree.md
new file mode 100644
index 0000000000..214097b35d
--- /dev/null
+++ b/.changeset/forty-ties-disagree.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-app-api': patch
+---
+
+Support icon overriding with the new `IconBundleBlueprint` API.
diff --git a/.changeset/gentle-dryers-smile.md b/.changeset/gentle-dryers-smile.md
new file mode 100644
index 0000000000..b061461da1
--- /dev/null
+++ b/.changeset/gentle-dryers-smile.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-defaults': patch
+---
+
+The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`.
diff --git a/.changeset/green-planets-reflect.md b/.changeset/green-planets-reflect.md
new file mode 100644
index 0000000000..513f0480a5
--- /dev/null
+++ b/.changeset/green-planets-reflect.md
@@ -0,0 +1,6 @@
+---
+'@backstage/frontend-test-utils': patch
+'@backstage/frontend-app-api': patch
+---
+
+Added support for v2 extensions, which declare their inputs and outputs without using a data map.
diff --git a/.changeset/heavy-numbers-love.md b/.changeset/heavy-numbers-love.md
new file mode 100644
index 0000000000..c18d144636
--- /dev/null
+++ b/.changeset/heavy-numbers-love.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used.
diff --git a/.changeset/late-games-protect.md b/.changeset/late-games-protect.md
new file mode 100644
index 0000000000..584780f836
--- /dev/null
+++ b/.changeset/late-games-protect.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-react': minor
+---
+
+Add scaffolder option to display object items in separate rows on review page
diff --git a/.changeset/light-pianos-exercise.md b/.changeset/light-pianos-exercise.md
new file mode 100644
index 0000000000..4bf161eb06
--- /dev/null
+++ b/.changeset/light-pianos-exercise.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-graph': patch
+---
+
+Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws
diff --git a/.changeset/little-bulldogs-guess.md b/.changeset/little-bulldogs-guess.md
new file mode 100644
index 0000000000..3e646a033f
--- /dev/null
+++ b/.changeset/little-bulldogs-guess.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag.
diff --git a/.changeset/mean-apricots-perform.md b/.changeset/mean-apricots-perform.md
new file mode 100644
index 0000000000..c93514cb7f
--- /dev/null
+++ b/.changeset/mean-apricots-perform.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes': patch
+---
+
+Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export.
diff --git a/.changeset/mighty-geckos-kiss.md b/.changeset/mighty-geckos-kiss.md
new file mode 100644
index 0000000000..0761d8b257
--- /dev/null
+++ b/.changeset/mighty-geckos-kiss.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-node': patch
+---
+
+Update `patchMkdocsYmlPrebuild` to modify `repo_url` and `edit_uri` independently.
diff --git a/.changeset/modern-parrots-protect.md b/.changeset/modern-parrots-protect.md
new file mode 100644
index 0000000000..bb4df2c248
--- /dev/null
+++ b/.changeset/modern-parrots-protect.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-react': patch
+---
+
+support `ajv-errors` for scaffolder validation to allow for customizing the error messages
diff --git a/.changeset/neat-bears-divide.md b/.changeset/neat-bears-divide.md
new file mode 100644
index 0000000000..a94b17f74d
--- /dev/null
+++ b/.changeset/neat-bears-divide.md
@@ -0,0 +1,5 @@
+---
+'@backstage/config-loader': patch
+---
+
+The `env` option of `ConfigSources.default` now correctly allows undefined members.
diff --git a/.changeset/neat-gifts-join.md b/.changeset/neat-gifts-join.md
new file mode 100644
index 0000000000..cd140c4692
--- /dev/null
+++ b/.changeset/neat-gifts-join.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-devtools-backend': patch
+---
+
+Removed unused code for lockfile analysis.
diff --git a/.changeset/new-scissors-try.md b/.changeset/new-scissors-try.md
new file mode 100644
index 0000000000..a9e9a45ce7
--- /dev/null
+++ b/.changeset/new-scissors-try.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend-module-microsoft-provider': patch
+---
+
+Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes.
diff --git a/.changeset/nine-glasses-nail.md b/.changeset/nine-glasses-nail.md
new file mode 100644
index 0000000000..138acce3dd
--- /dev/null
+++ b/.changeset/nine-glasses-nail.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-search-backend-module-pg': patch
+---
+
+Removing `@backstage/backend-app-api` dependency
diff --git a/.changeset/nine-seahorses-relate.md b/.changeset/nine-seahorses-relate.md
new file mode 100644
index 0000000000..9a5bb43823
--- /dev/null
+++ b/.changeset/nine-seahorses-relate.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-permission-common': patch
+---
+
+Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code.
diff --git a/.changeset/ninety-icons-smile.md b/.changeset/ninety-icons-smile.md
new file mode 100644
index 0000000000..f137879706
--- /dev/null
+++ b/.changeset/ninety-icons-smile.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Remove usage of deprecated functionality from @backstage/config-loader
diff --git a/.changeset/odd-books-share.md b/.changeset/odd-books-share.md
new file mode 100644
index 0000000000..858a2564ad
--- /dev/null
+++ b/.changeset/odd-books-share.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called.
diff --git a/.changeset/olive-books-sort.md b/.changeset/olive-books-sort.md
new file mode 100644
index 0000000000..d7bfd925ce
--- /dev/null
+++ b/.changeset/olive-books-sort.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-permission-node': patch
+---
+
+The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release.
diff --git a/.changeset/plenty-tools-exist.md b/.changeset/plenty-tools-exist.md
new file mode 100644
index 0000000000..0ce22a74f2
--- /dev/null
+++ b/.changeset/plenty-tools-exist.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-gitlab': patch
+---
+
+Added test cases for gitlab:issue:edit examples
diff --git a/.changeset/pre.json b/.changeset/pre.json
index be1b694c9e..7da5b582e1 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -188,5 +188,55 @@
"@backstage/plugin-user-settings-backend": "0.2.21",
"@backstage/plugin-user-settings-common": "0.0.1"
},
- "changesets": []
+ "changesets": [
+ "blue-pumas-cheer",
+ "bright-trainers-brake",
+ "chilly-trains-sleep",
+ "cool-insects-remember",
+ "cuddly-zebras-crash",
+ "dry-squids-tap",
+ "dull-ghosts-double",
+ "early-trees-dance",
+ "eighty-mirrors-flow",
+ "flat-plums-grow",
+ "forty-ties-agree",
+ "forty-ties-disagree",
+ "friendly-feet-refuse",
+ "gentle-dryers-smile",
+ "grumpy-owls-suffer",
+ "heavy-numbers-love",
+ "hip-hairs-exist",
+ "late-games-protect",
+ "light-pianos-exercise",
+ "little-bulldogs-guess",
+ "little-suns-fly",
+ "mighty-dolls-retire",
+ "modern-parrots-protect",
+ "neat-gifts-join",
+ "new-scissors-try",
+ "nice-peas-retire",
+ "nine-cherries-decide",
+ "nine-glasses-nail",
+ "ninety-icons-smile",
+ "pink-gorillas-brake",
+ "plenty-tools-exist",
+ "rich-mugs-dress",
+ "selfish-bees-think",
+ "six-rats-kick",
+ "slow-toes-jog",
+ "small-spoons-shout",
+ "soft-gorillas-refuse",
+ "spotty-planets-accept",
+ "strange-papayas-beg",
+ "strong-otters-compete",
+ "stupid-dots-relate",
+ "swift-kings-sparkle",
+ "thirty-paws-hope",
+ "tiny-oranges-pretend",
+ "tough-lies-repair",
+ "violet-jokes-wave",
+ "wicked-bobcats-teach",
+ "wild-eggs-exist",
+ "witty-bears-behave"
+ ]
}
diff --git a/.changeset/rare-foxes-compete.md b/.changeset/rare-foxes-compete.md
new file mode 100644
index 0000000000..64dd9b557e
--- /dev/null
+++ b/.changeset/rare-foxes-compete.md
@@ -0,0 +1,57 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Extensions have been changed to be declared with an array of inputs and outputs, rather than a map of named data refs. This change was made to reduce confusion around the role of the input and output names, as well as enable more powerful APIs for overriding extensions.
+
+An extension that was previously declared like this:
+
+```tsx
+const exampleExtension = createExtension({
+ name: 'example',
+ inputs: {
+ items: createExtensionInput({
+ element: coreExtensionData.reactElement,
+ }),
+ },
+ output: {
+ element: coreExtensionData.reactElement,
+ },
+ factory({ inputs }) {
+ return {
+ element: (
+
+ Example
+ {inputs.items.map(item => {
+ return
{item.output.element}
;
+ })}
+
+ ),
+ };
+ },
+});
+```
+
+Should be migrated to the following:
+
+```tsx
+const exampleExtension = createExtension({
+ name: 'example',
+ inputs: {
+ items: createExtensionInput([coreExtensionData.reactElement]),
+ },
+ output: [coreExtensionData.reactElement],
+ factory({ inputs }) {
+ return [
+ coreExtensionData.reactElement(
+
+ Example
+ {inputs.items.map(item => {
+ return
{item.get(coreExtensionData.reactElement)}
;
+ })}
+
,
+ ),
+ ];
+ },
+});
+```
diff --git a/.changeset/red-radios-promise.md b/.changeset/red-radios-promise.md
new file mode 100644
index 0000000000..ea876c3480
--- /dev/null
+++ b/.changeset/red-radios-promise.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-gitlab': patch
+---
+
+Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider.
diff --git a/.changeset/rich-mugs-dress.md b/.changeset/rich-mugs-dress.md
new file mode 100644
index 0000000000..5008e33a70
--- /dev/null
+++ b/.changeset/rich-mugs-dress.md
@@ -0,0 +1,7 @@
+---
+'@backstage/config-loader': patch
+---
+
+Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and
+`ConfigSources.defaultForTargets`, which results in omission of a ConfigSource
+for the default app-config.yaml configuration file if it's not present.
diff --git a/.changeset/seven-eggs-admire.md b/.changeset/seven-eggs-admire.md
new file mode 100644
index 0000000000..d68b38e2d7
--- /dev/null
+++ b/.changeset/seven-eggs-admire.md
@@ -0,0 +1,5 @@
+---
+'@backstage/create-app': patch
+---
+
+Updated dockerfile and `app-config.production.yaml` to make it easier to get started with example data
diff --git a/.changeset/shaggy-dodos-applaud.md b/.changeset/shaggy-dodos-applaud.md
new file mode 100644
index 0000000000..b7f968aa39
--- /dev/null
+++ b/.changeset/shaggy-dodos-applaud.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`.
diff --git a/.changeset/six-rats-kick.md b/.changeset/six-rats-kick.md
new file mode 100644
index 0000000000..23958a2502
--- /dev/null
+++ b/.changeset/six-rats-kick.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-gitlab': patch
+---
+
+Added test cases for gitlab:projectAccessToken:create example
diff --git a/.changeset/slow-toes-jog.md b/.changeset/slow-toes-jog.md
new file mode 100644
index 0000000000..245feab544
--- /dev/null
+++ b/.changeset/slow-toes-jog.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-home': patch
+---
+
+Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish
diff --git a/.changeset/small-spoons-shout.md b/.changeset/small-spoons-shout.md
new file mode 100644
index 0000000000..76c209edc6
--- /dev/null
+++ b/.changeset/small-spoons-shout.md
@@ -0,0 +1,13 @@
+---
+'@backstage/cli': minor
+---
+
+**BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands.
+
+The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files.
+
+The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic.
+
+The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches.
+
+The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future.
diff --git a/.changeset/soft-gorillas-refuse.md b/.changeset/soft-gorillas-refuse.md
new file mode 100644
index 0000000000..323c4d20d2
--- /dev/null
+++ b/.changeset/soft-gorillas-refuse.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-test-utils': patch
+---
+
+The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service.
diff --git a/.changeset/spicy-planets-provide.md b/.changeset/spicy-planets-provide.md
new file mode 100644
index 0000000000..f2929df850
--- /dev/null
+++ b/.changeset/spicy-planets-provide.md
@@ -0,0 +1,46 @@
+---
+'@backstage/plugin-notifications-backend-module-email': minor
+---
+
+**BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`.
+
+Required changes and example usage:
+
+```diff
+import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email';
+import { Notification } from '@backstage/plugin-notifications-common';
++import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library`
+export const notificationsModuleEmailDecorator = createBackendModule({
+ pluginId: 'notifications',
+ moduleId: 'email.templates',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ emailTemplates: notificationsEmailTemplateExtensionPoint,
+ },
+ async init({ emailTemplates }) {
+ emailTemplates.setTemplateRenderer({
+- getSubject(notification) {
++ async getSubject(notification) {
+- return `New notification from ${notification.source}`;
++ const subject = await getNotificationSubject(notification);
++ return `New notification from ${subject}`;
+ },
+- getText(notification) {
++ async getText(notification) {
+- return notification.content;
++ const text = await getNotificationTextContent(notification);
++ return text;
+ },
+- getHtml(notification) {
++ async getHtml(notification) {
+- return `${notification.content}
`;
++ const html = await getNotificationHtmlContent(notification);
++ return html;
+ },
+ });
+ },
+ });
+ },
+});
+```
diff --git a/.changeset/spotty-planets-accept.md b/.changeset/spotty-planets-accept.md
new file mode 100644
index 0000000000..7342f89bd1
--- /dev/null
+++ b/.changeset/spotty-planets-accept.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Cleaned up codebase of RepoUrlPicker
diff --git a/.changeset/strange-papayas-beg.md b/.changeset/strange-papayas-beg.md
new file mode 100644
index 0000000000..6ce33fb029
--- /dev/null
+++ b/.changeset/strange-papayas-beg.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-gitlab': patch
+---
+
+Added test cases for gitlab:pipeline:trigger examples
diff --git a/.changeset/strong-otters-compete.md b/.changeset/strong-otters-compete.md
new file mode 100644
index 0000000000..fdcb982b88
--- /dev/null
+++ b/.changeset/strong-otters-compete.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`.
diff --git a/.changeset/sweet-oranges-buy.md b/.changeset/sweet-oranges-buy.md
new file mode 100644
index 0000000000..d9f3343e3d
--- /dev/null
+++ b/.changeset/sweet-oranges-buy.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances.
diff --git a/.changeset/tall-snakes-fix.md b/.changeset/tall-snakes-fix.md
new file mode 100644
index 0000000000..351c781375
--- /dev/null
+++ b/.changeset/tall-snakes-fix.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-plugin-api': patch
+---
+
+fix typo in `getPluginRequestToken` comments
diff --git a/.changeset/thick-hotels-know.md b/.changeset/thick-hotels-know.md
new file mode 100644
index 0000000000..f0c7129256
--- /dev/null
+++ b/.changeset/thick-hotels-know.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Fix scaffolder action `catalog:write` to write to directories that don't already exist
diff --git a/.changeset/thirty-paws-hope.md b/.changeset/thirty-paws-hope.md
new file mode 100644
index 0000000000..0de607f331
--- /dev/null
+++ b/.changeset/thirty-paws-hope.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-app-api': patch
+---
+
+The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout.
diff --git a/.changeset/tiny-oranges-pretend.md b/.changeset/tiny-oranges-pretend.md
new file mode 100644
index 0000000000..87227fb962
--- /dev/null
+++ b/.changeset/tiny-oranges-pretend.md
@@ -0,0 +1,19 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+Extension data references can now be defined in a way that encapsulates the ID string in the type, in addition to the data type itself. The old way of creating extension data references is deprecated and will be removed in a future release.
+
+For example, the following code:
+
+```ts
+export const myExtension = createExtensionDataRef('my-plugin.my-data');
+```
+
+Should be updated to the following:
+
+```ts
+export const myExtension = createExtensionDataRef().with({
+ id: 'my-plugin.my-data',
+});
+```
diff --git a/.changeset/tough-lies-repair.md b/.changeset/tough-lies-repair.md
new file mode 100644
index 0000000000..b2f848e292
--- /dev/null
+++ b/.changeset/tough-lies-repair.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Fix issue with `RepoUrlPicker` not refreshing the credentials for a different host
diff --git a/.changeset/violet-jokes-wave.md b/.changeset/violet-jokes-wave.md
new file mode 100644
index 0000000000..623cff2583
--- /dev/null
+++ b/.changeset/violet-jokes-wave.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Fix for `repo build --all` not properly detecting the experimental public entry point.
diff --git a/.changeset/wicked-bobcats-teach.md b/.changeset/wicked-bobcats-teach.md
new file mode 100644
index 0000000000..041e336f7d
--- /dev/null
+++ b/.changeset/wicked-bobcats-teach.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-react': patch
+---
+
+feat: Hide visibility of CookieAuthRedirect
diff --git a/.changeset/wild-eggs-exist.md b/.changeset/wild-eggs-exist.md
new file mode 100644
index 0000000000..68c49e343b
--- /dev/null
+++ b/.changeset/wild-eggs-exist.md
@@ -0,0 +1,10 @@
+---
+'@backstage/plugin-scaffolder-backend-module-gitlab': patch
+---
+
+Add custom action for merge request: **auto**
+
+The **Auto** action selects the committed action between _create_ and _update_.
+
+The **Auto** action fetches files using the **/projects/repository/tree endpoint**.
+After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**.
diff --git a/.changeset/witty-bears-behave.md b/.changeset/witty-bears-behave.md
new file mode 100644
index 0000000000..174467b9eb
--- /dev/null
+++ b/.changeset/witty-bears-behave.md
@@ -0,0 +1,5 @@
+---
+'@backstage/integration': patch
+---
+
+Updated functions for `getHarnessEditContentsUrl`, `getHarnessFileContentsUrl`, `getHarnessArchiveUrl`, `getHarnessLatestCommitUrl` and `parseHarnessUrl` to handle account and org level urls
diff --git a/.changeset/young-games-visit.md b/.changeset/young-games-visit.md
new file mode 100644
index 0000000000..467abcec86
--- /dev/null
+++ b/.changeset/young-games-visit.md
@@ -0,0 +1,14 @@
+---
+'@backstage/frontend-plugin-api': patch
+'@backstage/frontend-test-utils': patch
+'@backstage/frontend-app-api': patch
+'@backstage/core-compat-api': patch
+'@backstage/plugin-catalog-react': patch
+'@backstage/plugin-user-settings': patch
+'@backstage/plugin-search-react': patch
+'@backstage/plugin-techdocs': patch
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-search': patch
+---
+
+Added config input type to the extensions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 9cd3bc0690..a0c83a42bd 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -9,10 +9,10 @@ yarn.lock @backstage/maintainers @backst
*/yarn.lock @backstage/maintainers @backstage-service
/.changeset/*.md
/beps/0001-notifications-system @backstage/maintainers @backstage/notifications-maintainers
-/docs/assets/search @backstage/discoverability-maintainers
-/docs/features/search @backstage/discoverability-maintainers
+/docs/assets/search @backstage/search-maintainers
+/docs/features/search @backstage/search-maintainers
/docs/features/techdocs @backstage/techdocs-maintainers
-/docs/plugins/integrating-search-into-plugins.md @backstage/discoverability-maintainers
+/docs/plugins/integrating-search-into-plugins.md @backstage/search-maintainers
/packages/cli/src/commands/onboard @backstage/sharks
/packages/backend-openapi-utils @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers
/packages/techdocs-cli @backstage/techdocs-maintainers
@@ -50,8 +50,8 @@ yarn.lock @backstage/maintainers @backst
/plugins/explore-react @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers
/plugins/fossa @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers
/plugins/git-release-manager @backstage/maintainers @backstage/reviewers @erikengervall
-/plugins/home @backstage/discoverability-maintainers
-/plugins/home-* @backstage/discoverability-maintainers
+/plugins/home @backstage/home-maintainers
+/plugins/home-* @backstage/home-maintainers
/plugins/kafka @backstage/maintainers @backstage/reviewers @andrewthauer
/plugins/kafka-backend @backstage/maintainers @backstage/reviewers @andrewthauer
/plugins/kubernetes @backstage/kubernetes-maintainers
@@ -70,13 +70,11 @@ yarn.lock @backstage/maintainers @backst
/plugins/rollbar-backend @backstage/maintainers @backstage/reviewers @andrewthauer
/plugins/scaffolder @backstage/maintainers @backstage/reviewers @backstage/scaffolder-maintainers
/plugins/scaffolder-* @backstage/maintainers @backstage/reviewers @backstage/scaffolder-maintainers
-/plugins/search @backstage/discoverability-maintainers
-/plugins/search-* @backstage/discoverability-maintainers
+/plugins/search @backstage/search-maintainers
+/plugins/search-* @backstage/search-maintainers
/plugins/signals @backstage/maintainers @backstage/notifications-maintainers
/plugins/signals-* @backstage/maintainers @backstage/notifications-maintainers
/plugins/sonarqube @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers
-/plugins/stack-overflow @backstage/discoverability-maintainers
-/plugins/stack-overflow-backend @backstage/discoverability-maintainers
/plugins/techdocs @backstage/techdocs-maintainers
/plugins/techdocs-* @backstage/techdocs-maintainers
/plugins/user-settings-backend @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers
diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml
index 6d74ce8902..c6c6ef115f 100644
--- a/.github/issue-labeler.yml
+++ b/.github/issue-labeler.yml
@@ -1,6 +1,6 @@
area:techdocs:
- '/(techdocs|tech-docs|tech docs)/i'
-area:discoverability:
+area:search:
- '/search/i'
area:events:
- '/events/i'
diff --git a/.github/labeler.yml b/.github/labeler.yml
index a58da1d2f7..e6568091b8 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -4,7 +4,11 @@ area:catalog:
- plugins/catalog/**/*
- plugins/catalog-*/**/*
- packages/catalog-*/**/*
-area:discoverability: # search + home
+area:home:
+ - changed-files:
+ - any-glob-to-any-file:
+ - plugins/home/**/*
+area:search:
- changed-files:
- any-glob-to-any-file:
- plugins/search/**/*
diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt
index 6ff0ef56da..42b0e4895c 100644
--- a/.github/vale/config/vocabularies/Backstage/accept.txt
+++ b/.github/vale/config/vocabularies/Backstage/accept.txt
@@ -7,6 +7,7 @@ ADRs
airbrake
Airbrake
Airbrakes
+ajv
Alaria
Alef
allowlisted
@@ -84,7 +85,9 @@ debounce
Debounce
debuggability
declaratively
+deduplicate
deduplicated
+deduplication
deliverables
denormalized
dependabot
diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml
index e334a29b82..c39fdf7f7c 100644
--- a/.github/workflows/api-breaking-changes.yml
+++ b/.github/workflows/api-breaking-changes.yml
@@ -27,7 +27,7 @@ jobs:
run: git fetch --depth 1 origin ${{ github.base_ref }}
- name: setup-node
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 135076ba40..168d6205e4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -71,7 +71,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -216,7 +216,7 @@ jobs:
run: git fetch origin ${{ github.event.pull_request.base.ref }}
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml
index 8b4709337a..9f305f9a20 100644
--- a/.github/workflows/deploy_docker-image.yml
+++ b/.github/workflows/deploy_docker-image.yml
@@ -31,7 +31,7 @@ jobs:
ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }}
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml
index a0cdae5774..be46bcfaa0 100644
--- a/.github/workflows/deploy_microsite.yml
+++ b/.github/workflows/deploy_microsite.yml
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml
index db9489dbb8..5488b9a189 100644
--- a/.github/workflows/deploy_nightly.yml
+++ b/.github/workflows/deploy_nightly.yml
@@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml
index 81e95fab97..7ae5637986 100644
--- a/.github/workflows/deploy_packages.yml
+++ b/.github/workflows/deploy_packages.yml
@@ -77,7 +77,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -161,7 +161,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index ecf18d71ad..354a76760b 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
- uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/upload-sarif@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14
with:
sarif_file: results.sarif
diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml
index 9886c94830..da6bd2592d 100644
--- a/.github/workflows/sync_code-formatting.yml
+++ b/.github/workflows/sync_code-formatting.yml
@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml
index 6d02309f8a..62dd7e0c13 100644
--- a/.github/workflows/sync_release-manifest.yml
+++ b/.github/workflows/sync_release-manifest.yml
@@ -19,7 +19,7 @@ jobs:
ref: v${{ github.event.client_payload.version }}
- name: use node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml
index 1cdc0cb22a..af2e4da251 100644
--- a/.github/workflows/sync_snyk-github-issues.yml
+++ b/.github/workflows/sync_snyk-github-issues.yml
@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -29,7 +29,7 @@ jobs:
cache-prefix: ${{ runner.os }}-v18.x
- name: Create Snyk report
- uses: snyk/actions/node@640e31719aac3e44867d239dc86c20c3e34c8e4f # master
+ uses: snyk/actions/node@6312a53377a551c0258438bf25fb8f378afbc977 # 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 b1e50397ce..1bdf0d30cf 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@640e31719aac3e44867d239dc86c20c3e34c8e4f # master
+ uses: snyk/actions/node@6312a53377a551c0258438bf25fb8f378afbc977 # 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@640e31719aac3e44867d239dc86c20c3e34c8e4f # master
+ uses: snyk/actions/node@6312a53377a551c0258438bf25fb8f378afbc977 # 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@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/upload-sarif@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14
with:
sarif_file: snyk.sarif
diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml
index bb46bbd4c1..386306dcb1 100644
--- a/.github/workflows/verify_accessibility.yml
+++ b/.github/workflows/verify_accessibility.yml
@@ -26,7 +26,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Use Node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
- name: yarn install
diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml
index e19187b6ce..b2fb2dc6ec 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@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/init@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14
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@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/autobuild@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14
# âšī¸ 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@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
+ uses: github/codeql-action/analyze@5cf07d8b700b67e235fbb65cbc84f69c0cf10464 # v3.25.14
diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml
index b7465945ea..4647505cbf 100644
--- a/.github/workflows/verify_e2e-kubernetes.yml
+++ b/.github/workflows/verify_e2e-kubernetes.yml
@@ -29,7 +29,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml
index d6491f1e3c..fa303ce1c6 100644
--- a/.github/workflows/verify_e2e-linux.yml
+++ b/.github/workflows/verify_e2e-linux.yml
@@ -53,7 +53,7 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml
index d7575cadf7..bc782c4bd3 100644
--- a/.github/workflows/verify_e2e-windows.yml
+++ b/.github/workflows/verify_e2e-windows.yml
@@ -50,7 +50,7 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml
index 1a5bc8075b..08dcddde8e 100644
--- a/.github/workflows/verify_microsite.yml
+++ b/.github/workflows/verify_microsite.yml
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
- uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5
diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml
index c9ed1696cd..220e2633c3 100644
--- a/.github/workflows/verify_microsite_accessibility.yml
+++ b/.github/workflows/verify_microsite_accessibility.yml
@@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Use Node.js 18.x
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: 18.x
diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml
index 83ddac8429..2f93b67616 100644
--- a/.github/workflows/verify_storybook.yml
+++ b/.github/workflows/verify_storybook.yml
@@ -37,7 +37,7 @@ jobs:
fetch-depth: 0 # Required to retrieve git history
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml
index 736193cad6..109e2ee96e 100644
--- a/.github/workflows/verify_windows.yml
+++ b/.github/workflows/verify_windows.yml
@@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
+ uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 870b4c93d4..3b534052f1 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -274,3 +274,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/
| [Grupo OLX](https://www.olx.com.br/) | [@marciorgb](https://github.com/marciorgb), [@jeffbraga](https://github.com/jeffbraga), [@rogerfernandes](https://github.com/rogerfernandes), [@stockrt](https://github.com/stockrt) | At Grupo OLX, we are using Backstage to reduce friction and enhance developer autonomy by creating Golden Paths, which automate previously manual processes. Additionally, Backstage's catalog integrates various views of our organizational structure and workflows, offering a comprehensive overview of how different components interconnect. |
| [DPG Media](https://www.dpgmediagroup.com) | [@benjidotsh](https://github.com/benjidotsh), [@lvstb](https://github.com/lvstb), [@ssliman](https://github.com/ssliman), [@edewel-beep](https://github.com/edewel-beep) | Access to infrastructure, documentation and internal tooling |
| [Baloise](https://www.baloise.ch) | [@dweber019](https://github.com/dweber019), [@mikevader](https://github.com/mikevader) | We are using Backstage as internal developer protal for use cases like software catalog, documentation, infrastructure. |
+| [Cigna](https://www.cigna.com/) / [Evernorth](https://www.evernorth.com/) | [@raphtalia](https://github.com/raphtalia), [Devon Paluso](mailto:Devon.Paluso@evernorth.com), [Bryan Rego](mailto:Bryan.Rego@evernorth.com), [Ian Skillings](mailto:Ian.Skillings@evernorth.com) | Internal developer portal providing TechDocs, software templates, centralized secret management, with information consolidation from ServiceNow, Okta, GitHub, GitLab, and WebEx. |
diff --git a/OWNERS.md b/OWNERS.md
index 5012181d21..b145205539 100644
--- a/OWNERS.md
+++ b/OWNERS.md
@@ -32,11 +32,11 @@ Scope: The catalog plugin and catalog model
| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` |
| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` |
-### Discoverability
+### Home
-Team: @backstage/discoverability-maintainers
+Team: @backstage/home-maintainers
-Scope: Discoverability within Backstage, including the home page, information architecture, and search
+Scope: The Backstage home page and information architecture
| Name | Organization | Team | GitHub | Discord |
| ------------------------ | ------------ | -------------- | ---------------------------------------- | ----------------- |
@@ -46,6 +46,18 @@ Scope: Discoverability within Backstage, including the home page, information ar
| Raghunandan Balachandran | Spotify | Infinite Buck$ | [soapraj](http://github.com/soapraj) | raghunandanb#1114 |
| Bailey Brooks | Spotify | Infinite Buck$ | [bailey](http://github.com/bailey) | \_bailey4 |
+### Search
+
+Team: @backstage/search-maintainers
+
+Scope: The Backstage Search plugin
+
+| Name | Organization | Team | GitHub | Discord |
+| ------------------------ | ------------ | -------------- | --------------------------------------------- | ----------------- |
+| Emma Indal | Spotify | Infinite Buck$ | [emmaindal](http://github.com/emmaindal) | emmaindal#7503 |
+| Raghunandan Balachandran | Spotify | Infinite Buck$ | [soapraj](http://github.com/soapraj) | raghunandanb#1114 |
+| Alex Lorenzi | Spotify | ProTean | [AlexLorenzi](https://github.com/AlexLorenzi) | alexlorenzi |
+
### Helm Charts
Team: @backstage/helm-chart-maintainers
diff --git a/docs/assets/getting-started/login-screen.png b/docs/assets/getting-started/login-screen.png
new file mode 100755
index 0000000000..1af52cb2c9
Binary files /dev/null and b/docs/assets/getting-started/login-screen.png differ
diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md
index f799eeb627..60104b99b7 100644
--- a/docs/auth/microsoft/provider.md
+++ b/docs/auth/microsoft/provider.md
@@ -67,8 +67,6 @@ auth:
clientSecret: ${AZURE_CLIENT_SECRET}
tenantId: ${AZURE_TENANT_ID}
domainHint: ${AZURE_TENANT_ID}
- additionalScopes:
- - Mail.Send
signIn:
resolvers:
# typically you would pick one of these
@@ -86,7 +84,7 @@ The Microsoft provider is a structure with three mandatory configuration keys:
Leave blank if your app registration is multi tenant.
When specified, this reduces login friction for users with accounts in multiple tenants by automatically filtering away accounts from other tenants.
For more details, see [Home Realm Discovery](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/home-realm-discovery-policy)
-- `additionalScopes` (optional): List of scopes for the App Registration. The default and mandatory value is ['user.read'].
+- `additionalScopes` (optional): List of scopes for the App Registration, to be requested in addition to the required ones.
### Resolvers
diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md
index c6db44078e..67104c7ba9 100644
--- a/docs/backend-system/building-backends/08-migrating.md
+++ b/docs/backend-system/building-backends/08-migrating.md
@@ -497,7 +497,7 @@ catalog:
providers:
githubOrg:
- id: production
- githubUrl: 'https://github.com',
+ githubUrl: 'https://github.com'
orgs: ['backstage']
schedule:
frequency: PT30M
@@ -536,8 +536,8 @@ catalog:
providers:
githubOrg:
- id: production
- githubUrl: 'https://github.com',
- orgs: ['org-a', 'org-b'],
+ githubUrl: 'https://github.com'
+ orgs: ['org-a', 'org-b']
schedule:
frequency: PT30M
timeout: PT15M
diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md
index 154f68c7d1..d47cb248f8 100644
--- a/docs/deployment/docker.md
+++ b/docs/deployment/docker.md
@@ -92,11 +92,14 @@ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
yarn install --frozen-lockfile --production --network-timeout 300000
+# This will include the examples, if you don't need these simply remove this line
+COPY --chown=node:node examples ./examples
+
# Then copy the rest of the backend bundle, along with any other files we might want.
COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
-CMD ["node", "packages/backend", "--config", "app-config.yaml"]
+CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]
```
For more details on how the `backend:bundle` command and the `skeleton.tar.gz`
@@ -251,12 +254,15 @@ RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid
COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./
# Copy any other files that we need at runtime
-COPY --chown=node:node app-config.yaml ./
+COPY --chown=node:node app-config*.yaml ./
+
+# This will include the examples, if you don't need these simply remove this line
+COPY --chown=node:node examples ./examples
# This switches many Node.js dependencies to production mode.
ENV NODE_ENV production
-CMD ["node", "packages/backend", "--config", "app-config.yaml"]
+CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]
```
Note that a newly created Backstage app will typically not have a `plugins/`
@@ -277,6 +283,7 @@ packages/*/dist
packages/*/node_modules
plugins/*/dist
plugins/*/node_modules
+*.local.yaml
```
Once you have added both the `Dockerfile` and `.dockerignore` to the root of
@@ -312,14 +319,12 @@ first step in doing so is to remove the `app-backend` plugin from the backend
package, which is done as follows:
1. Delete `packages/backend/src/plugins/app.ts`
-2. Remove the following lines from `packages/backend/src/index.ts`:
- ```tsx
- import app from './plugins/app';
- // ...
- const appEnv = useHotMemoize(module, () => createEnv('app'));
- // ...
- .addRouter('', await app(appEnv));
+2. Remove the following line from `packages/backend/src/index.ts`:
+
+ ```ts
+ backend.add(import('@backstage/plugin-app-backend/alpha'));
```
+
3. Remove the `@backstage/plugin-app-backend` and the app package dependency
(e.g. `app`) from `packages/backend/package.json`. If you don't remove the
app package dependency the app will still be built and bundled with the
diff --git a/docs/deployment/heroku.md b/docs/deployment/heroku.md
index 1545c34733..51d0b4b439 100644
--- a/docs/deployment/heroku.md
+++ b/docs/deployment/heroku.md
@@ -5,40 +5,37 @@ sidebar_label: Heroku
description: How to deploy Backstage to Heroku
---
-Heroku is a Platform as a Service (PaaS) designed to handle application
-deployment in a hands-off way. Heroku supports container deployment of Docker
-images, a natural fit for Backstage.
+Heroku is a Platform as a Service (PaaS) designed to simplify application deployment.
-## Configuring the CLI
+## Create App
-First, install the
-[heroku-cli](https://devcenter.heroku.com/articles/heroku-cli) and login:
+Starting with an existing Backstage app or follow the [getting started guide](https://backstage.io/docs/getting-started/) to create a new one.
+
+Install the
+[Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) and create a new Heroku app:
```shell
-$ heroku login
+cd your-app/
+heroku apps:create
```
-If you have not yet created a project through the Heroku interface, you can create it through the CLI.
+## Domain
+
+Get Heroku app URL:
```shell
-$ heroku create
+heroku domains -a
+.herokuapp.com
```
-You _might_ also need to set your Heroku app's stack to `container`:
-
-```bash
-$ heroku stack:set container -a
-```
-
-Configuring your `app-config.yaml`:
+The core [app-backend plugin](https://www.npmjs.com/package/@backstage/plugin-app-backend) allows a single Heroku app to serve the frontend and backend. To make this work you need to update the `baseUrl` and `port` in `app-config.production.yaml`:
```yaml
app:
- # Should be the same as backend.baseUrl when using the `app-backend` plugin
- baseUrl: https://.herokuapp.com
+ baseUrl: https://.herokuapp.com
backend:
- baseUrl: https://.herokuapp.com
+ baseUrl: https://.herokuapp.com
listen:
port:
$env: PORT
@@ -46,28 +43,98 @@ backend:
# https://devcenter.heroku.com/articles/dynos#web-dynos
```
-> Make sure your file is being copied into your container in the `Dockerfile`.
+## Build Script
-Before building the Docker image, run the [backstage host build commands](https://backstage.io/docs/deployment/docker#host-build). They must be run whenever you are going to publish a new image.
+Add a build script in `package.json` to compile frontend during deployment:
-Heroku runs a container registry on `registry.heroku.com`. To push Backstage
-Docker images, log in to the container registry also:
+```json
+"scripts": {
+ "build": "yarn build:backend --config ../../app-config.yaml --config ../../app-config.production.yaml"
+```
+
+## Start Command
+
+Create a [Procfile](https://devcenter.heroku.com/articles/procfile) in the app's root:
```shell
-$ heroku container:login
+echo "web: yarn workspace backend start --config ../../app-config.yaml --config ../../app-config.production.yaml" > Procfile
```
-## Push and deploy a Docker image
+## Database
-Now we can push a Backstage [Docker image](docker.md) to Heroku's container
-registry and release it to the `web` worker:
+Provision a [Heroku Postgres](https://elements.heroku.com/addons/heroku-postgresql) database:
-```bash
-$ docker image build . -f packages/backend/Dockerfile --tag registry.heroku.com//web
-
-$ docker push registry.heroku.com//web
-
-$ heroku container:release web -a
+```shell
+heroku addons:create heroku-postgresql -a
```
-Now you should have Backstage up and running! đ
+Update `database` in `app-config.production.yaml`:
+
+```yaml
+backend:
+ database:
+ client: pg
+ pluginDivisionMode: schema
+ ensureExists: false
+ ensureSchemaExists: true
+ connection: ${DATABASE_URL}
+```
+
+Allow postgres self-signed certificates:
+
+```shell
+heroku config:set PGSSLMODE=no-verify -a
+```
+
+## Deployment
+
+Commit changes and push to Heroku to build and deploy:
+
+```shell
+git add Procfile && git commit -am "configure heroku"
+git push heroku main
+```
+
+View the app in the browser:
+
+```shell
+heroku open -a
+```
+
+View logs:
+
+```shell
+heroku logs -a
+```
+
+## Docker
+
+As an alternative to git deploys, Heroku also [supports container images](https://devcenter.heroku.com/articles/container-registry-and-runtime).
+
+Login to Heroku's container registry:
+
+```shell
+heroku container:login
+```
+
+Configure the Heroku app to run a container image:
+
+```shell
+heroku stack:set container -a
+```
+
+Locally run the [host build commands](https://backstage.io/docs/deployment/docker/#host-build), they must be run whenever you are going to publish a new image:
+
+```shell
+yarn install --frozen-lockfile
+yarn tsc
+yarn build:backend --config ../../app-config.yaml --config ../../app-config.production.yaml
+```
+
+Build, push, and release the container image to the `web` dyno:
+
+```shell
+docker image build . -f packages/backend/Dockerfile --tag registry.heroku.com//web
+docker push registry.heroku.com//web
+heroku container:release web -a
+```
diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md
index 17d5f49205..67382aa6ce 100644
--- a/docs/features/software-catalog/configuration.md
+++ b/docs/features/software-catalog/configuration.md
@@ -58,6 +58,20 @@ catalog:
target: ../../examples/all.yaml
```
+:::note
+There might be cases where you need to test some `file` configurations in a Docker container. In a case like this, as the backend is serving the frontend in a default setup, the path would be from the root. Also, you need to **make sure to copy your files into your container**.
+
+Using the example above that would look like this:
+
+```yaml
+catalog:
+ locations:
+ - type: file
+ target: ./examples/all.yaml
+```
+
+:::
+
### Integration Processors
Integrations may simply provide a mechanism to handle `url` location type for an
diff --git a/docs/features/software-templates/input-examples.md b/docs/features/software-templates/input-examples.md
index f49e76e469..dd1228290f 100644
--- a/docs/features/software-templates/input-examples.md
+++ b/docs/features/software-templates/input-examples.md
@@ -26,6 +26,25 @@ parameters:
ui:help: 'Hint: additional description...'
```
+#### Custom validation error message
+
+```yaml
+parameters:
+ - title: Fill in some steps
+ properties:
+ name:
+ title: Simple text input
+ type: string
+ description: Description about input
+ maxLength: 8
+ pattern: '^([a-zA-Z][a-zA-Z0-9]*)(-[a-zA-Z0-9]+)*$'
+ ui:autofocus: true
+ ui:help: 'Hint: additional description...'
+ errorMessage:
+ properties:
+ name: '1-8 alphanumeric tokens (first starts with letter) delimited by -'
+```
+
### Multi line text input
```yaml
diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md
index 15591eeb67..075fe0bb08 100644
--- a/docs/features/software-templates/writing-templates.md
+++ b/docs/features/software-templates/writing-templates.md
@@ -533,6 +533,11 @@ catalogFilter:
metadata.annotations.github.com/team-slug: { exists: true }
```
+#### Custom validation messages
+
+You may specify custom JSON Schema validation messages as supported by the
+[ajv-errors](https://github.com/ajv-validator/ajv-errors) plugin library to [ajv](https://github.com/ajv-validator/ajv).
+
## `spec.steps` - `Action[]`
The `steps` is an array of the things that you want to happen part of this
diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md
index cdc73a17ec..2f3daba6f8 100644
--- a/docs/frontend-system/architecture/03-extensions.md
+++ b/docs/frontend-system/architecture/03-extensions.md
@@ -91,7 +91,9 @@ To create a new extension data reference to represent a type of shared extension
```ts
export const reactElementExtensionDataRef =
- createExtensionDataRef('my-plugin.reactElement');
+ createExtensionDataRef().with({
+ id: 'my-plugin.reactElement',
+ });
```
The `ExtensionDataRef` can then be used to describe an output property of the extension. This will enforce typing on the return value of the extension factory:
diff --git a/docs/frontend-system/architecture/08-naming-patterns.md b/docs/frontend-system/architecture/08-naming-patterns.md
index a17525f3ce..f031b80d84 100644
--- a/docs/frontend-system/architecture/08-naming-patterns.md
+++ b/docs/frontend-system/architecture/08-naming-patterns.md
@@ -98,9 +98,9 @@ export interface SearchResultItemExtensionData {
}
export const searchResultItemExtensionDataRef =
- createExtensionDataRef(
- 'search.search-result-item',
- );
+ createExtensionDataRef().with({
+ id: 'search.search-result-item',
+ });
```
#### Grouped Extension Data
@@ -109,8 +109,12 @@ This way of defining extension data is similar to the standalone way, but it use
```ts
export const coreExtensionData = {
- reactElement: createExtensionDataRef('core.react-element'),
- routePath: createExtensionDataRef('core.route-path'),
+ reactElement: createExtensionDataRef().with({
+ id: 'core.react-element',
+ }),
+ routePath: createExtensionDataRef().with({
+ id: 'core.route-path',
+ }),
};
```
@@ -125,9 +129,9 @@ export function createGraphiQLEndpointExtension(options) {
// Use a TypeScript namespace to merge the extension data references with the extension creator
export namespace createGraphiQLEndpointExtension {
- export const endpointDataRef = createExtensionDataRef* ... */>(
- 'graphiql.graphiql-endpoint.endpoint',
- );
+ export const endpointDataRef = createExtensionDataRef* ... */>().with({
+ id: 'graphiql.graphiql-endpoint.endpoint',
+ });
}
```
diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md
index 4f8417f74f..a5e6476eb0 100644
--- a/docs/frontend-system/building-apps/03-built-in-extensions.md
+++ b/docs/frontend-system/building-apps/03-built-in-extensions.md
@@ -45,6 +45,7 @@ This extension is the first extension attached to the extension tree. It is resp
| themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) |
| components | The app components list. | [createComponentExtension.componentDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension.componentdataref) | false | See [default components](#default-components-extensions). | [createComponentExtension](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension) |
| translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) |
+| icons | The app icons list. | [IconBundleBlueprint.dataRefs.icons](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint.dataRefs.icons) | true | - | [IconBundleBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint) |
#### Default theme extensions
diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md
index 0abdd2aac3..716e0da772 100644
--- a/docs/frontend-system/building-apps/08-migrating.md
+++ b/docs/frontend-system/building-apps/08-migrating.md
@@ -314,6 +314,31 @@ const app = createApp({
});
```
+### `icons`
+
+Icons are now installed as extensions, using the `IconBundleBlueprint` to make new instances which can be added to the app.
+
+```ts
+import { IconBundleBlueprint } from '@backstage/frontend-plugin-api';
+
+const exampleIconBundle = IconBundleBlueprint.make({
+ name: 'example-bundle',
+ params: {
+ icons: {
+ user: MyOwnUserIcon,
+ },
+ },
+});
+
+const app = createApp({
+ features: [
+ createExtensionOverrides({
+ extensions: [exampleIconBundle],
+ }),
+ ],
+});
+```
+
### `bindRoutes`
Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/07-routes.md#binding-external-route-references) for more information.
diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md
index 2bea073ed6..1f3baaa710 100644
--- a/docs/frontend-system/building-plugins/03-extension-types.md
+++ b/docs/frontend-system/building-plugins/03-extension-types.md
@@ -38,6 +38,10 @@ Sign-in page extension have a single purpose - to implement a custom sign-in pag
Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use.
+### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md)
+
+Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app.
+
### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md)
Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages.
diff --git a/docs/getting-started/config/authentication.md b/docs/getting-started/config/authentication.md
index 874964696a..e97ed3d89c 100644
--- a/docs/getting-started/config/authentication.md
+++ b/docs/getting-started/config/authentication.md
@@ -50,7 +50,6 @@ Open `packages/app/src/App.tsx` and below the last `import` line, add:
```typescript title="packages/app/src/App.tsx"
import { githubAuthApiRef } from '@backstage/core-plugin-api';
-import { SignInPage } from '@backstage/core-components';
```
Search for `const app = createApp({` in this file, and below `apis,` add:
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index 99fdb3d8ed..8bb30d1db8 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -1,6 +1,6 @@
---
id: index
-title: Installing a standalone server
+title: Creating your Backstage App
sidebar_label: Introduction
description: How to install Backstage for your own use.
---
diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md
index 1de8874d56..88e4a02ef2 100644
--- a/docs/getting-started/keeping-backstage-updated.md
+++ b/docs/getting-started/keeping-backstage-updated.md
@@ -63,18 +63,16 @@ When a given dependency version is the _same_ between different packages, the
dependency is hoisted to the main `node_modules` folder in the monorepo root to
be shared between packages. When _different_ versions of the same dependency are
encountered, Yarn creates a `node_modules` folder within a particular package.
+This can lead to multiple versions of the same package being installed and used
+in the same app.
-This can lead to confusing situations with type definitions, or anything with
-global state. React [Context](https://reactjs.org/docs/context.html), for
-example, depends on global referential equality. This can cause problems in
-Backstage with API lookup, or config loading.
+All Backstage core packages are implemented in such as way that package
+duplication is **not** a problem. For example, duplicate installations of
+packages like `@backstage/core-plugin-api`, `@backstage/core-components`,
+`@backstage/plugin-catalog-react`, and `@backstage/backend-plugin-api` are all
+acceptable.
-To help resolve these situations, the Backstage CLI has
-[versions:check](https://backstage.io/docs/tooling/cli/03-commands#versionscheck). This
-will validate versions of `@backstage` packages in your app to check for
-duplicate definitions:
-
-```bash
-# Add --fix to attempt automatic resolution in yarn.lock
-yarn backstage-cli versions:check
-```
+While package duplication might be acceptable in many cases, you might want to
+deduplicate packages for the purpose of optimizing bundle size and installation
+speed. We recommend using deduplication utilities such as `yarn dedupe` to trim
+down the number of duplicate packages.
diff --git a/docs/getting-started/logging-in.md b/docs/getting-started/logging-in.md
index fef8907e07..7d6fb5d55b 100644
--- a/docs/getting-started/logging-in.md
+++ b/docs/getting-started/logging-in.md
@@ -20,6 +20,8 @@ Run your Backstage app with `yarn dev`. Navigate to `http://localhost:3000`.
If you're not already logged in, you should see a login screen like this,
+
+
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.
diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md
index 13ffd79801..70f12f181e 100644
--- a/docs/integrations/gitlab/discovery.md
+++ b/docs/integrations/gitlab/discovery.md
@@ -153,6 +153,7 @@ catalog:
group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned
entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml`
projectPattern: '[\s\S]*' # Optional. Filters found projects based on provided patter. Defaults to `[\s\S]*`, which means to not filter anything
+ excludeRepos: [] # Optional. A list of project paths that should be excluded from discovery, e.g. group/subgroup/repo. Should not start or end with a slash.
schedule: # Same options as in TaskScheduleDefinition. Optional for the Legacy Backend System
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
diff --git a/docs/releases/v1.30.0-next.0-changelog.md b/docs/releases/v1.30.0-next.0-changelog.md
new file mode 100644
index 0000000000..f3b7f2ef45
--- /dev/null
+++ b/docs/releases/v1.30.0-next.0-changelog.md
@@ -0,0 +1,2214 @@
+# Release v1.30.0-next.0
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.30.0-next.0)
+
+## @backstage/cli@0.27.0-next.0
+
+### Minor Changes
+
+- 32a38e1: **BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands.
+
+ The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files.
+
+ The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic.
+
+ The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches.
+
+ The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future.
+
+### Patch Changes
+
+- 7eb08a6: Add frontend-dynamic-container role to eslint config factory
+- fbc7819: Use ES2022 in CLI bundler
+- e53074f: Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used.
+- ee2b0e5: The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag.
+- 239dffc: Remove usage of deprecated functionality from @backstage/config-loader
+- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`.
+- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point.
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/cli-node@0.2.7
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/eslint-plugin@0.1.8
+ - @backstage/release-manifests@0.0.11
+ - @backstage/types@1.1.1
+
+## @backstage/integration@1.14.0-next.0
+
+### Minor Changes
+
+- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens.
+
+### Patch Changes
+
+- c591670: Updated functions for `getHarnessEditContentsUrl`, `getHarnessFileContentsUrl`, `getHarnessArchiveUrl`, `getHarnessLatestCommitUrl` and `parseHarnessUrl` to handle account and org level urls
+- Updated dependencies
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+
+## @backstage/plugin-scaffolder-react@1.11.0-next.0
+
+### Minor Changes
+
+- 8839381: Add scaffolder option to display object items in separate rows on review page
+
+### Patch Changes
+
+- d0e95a7: Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps`
+- 4670f06: support `ajv-errors` for scaffolder validation to allow for customizing the error messages
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.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.24
+ - @backstage/plugin-scaffolder-common@1.5.4
+
+## @backstage/app-defaults@1.5.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-permission-react@0.4.24
+
+## @backstage/backend-app-api@0.8.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/cli-node@0.2.7
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
+## @backstage/backend-common@0.23.4-next.0
+
+### Patch Changes
+
+- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail.
+
+- 7e13b7a: The remaining exports in the package have now been deprecated:
+
+ - `cacheToPluginCacheManager`
+ - `createLegacyAuthAdapters`
+ - `LegacyCreateRouter`
+ - `legacyPlugin`
+ - `loggerToWinstonLogger`
+ - `makeLegacyPlugin`
+
+ Users of these export should fully [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating).
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-dev-utils@0.1.4
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @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-auth-node@0.4.18-next.0
+
+## @backstage/backend-defaults@0.4.2-next.0
+
+### Patch Changes
+
+- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`.
+- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens.
+- 8e967da: Fixed the routing of the new health check service, the health endpoints should now properly be available at `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness`.
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-app-api@0.8.1-next.0
+ - @backstage/backend-dev-utils@0.1.4
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @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-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
+## @backstage/backend-dynamic-feature-service@0.2.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-app-api@0.8.1-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/cli-node@0.2.7
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-app-node@0.1.23-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-backend@0.3.10-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/backend-openapi-utils@0.1.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/errors@1.2.4
+
+## @backstage/backend-plugin-api@0.7.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.1.14
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-permission-common@0.8.0
+
+## @backstage/backend-tasks@0.5.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+
+## @backstage/backend-test-utils@0.4.5-next.0
+
+### Patch Changes
+
+- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service.
+- Updated dependencies
+ - @backstage/backend-defaults@0.4.2-next.0
+ - @backstage/backend-app-api@0.8.1-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/config-loader@1.8.2-next.0
+
+### Patch Changes
+
+- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and
+ `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource
+ for the default app-config.yaml configuration file if it's not present.
+- 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.1-next.0
+
+### Patch Changes
+
+- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout.
+- Updated dependencies
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.8
+
+## @backstage/core-compat-api@0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/version-bridge@1.0.8
+
+## @backstage/core-components@0.14.10-next.0
+
+### Patch Changes
+
+- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component
+- 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.18-next.0
+
+### Patch Changes
+
+- c0a705d: Added the Kubernetes plugin to `create-app`
+- Updated dependencies
+ - @backstage/cli-common@0.1.14
+
+## @backstage/dev-utils@1.0.36-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/app-defaults@1.5.9-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/theme@0.5.6
+
+## @backstage/frontend-app-api@0.7.4-next.0
+
+### Patch Changes
+
+- 7777b5f: Support icon overriding with the new `IconBundleBlueprint` API.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/theme@0.5.6
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.8
+
+## @backstage/frontend-plugin-api@0.6.8-next.0
+
+### Patch Changes
+
+- 4e53ad6: Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint`
+
+ This allows the creation of extension instances with the following pattern:
+
+ ```tsx
+ // create the extension blueprint which is used to create instances
+ const EntityCardBlueprint = createExtensionBlueprint({
+ kind: 'entity-card',
+ attachTo: { id: 'test', input: 'default' },
+ output: {
+ element: coreExtensionData.reactElement,
+ },
+ factory(params: { text: string }) {
+ return {
+ element: {params.text}
,
+ };
+ },
+ });
+
+ // create an instance of the extension blueprint with params
+ const testExtension = EntityCardBlueprint.make({
+ name: 'foo',
+ params: {
+ text: 'Hello World',
+ },
+ });
+ ```
+
+- 9b89b82: The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path.
+
+- 7777b5f: Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons.
+
+ ```tsx
+ import { IconBundleBlueprint } from '@backstage/frontend-plugin-api';
+
+ const exampleIconBundle = IconBundleBlueprint.make({
+ name: 'example-bundle',
+ params: {
+ icons: {
+ user: MyOwnUserIcon,
+ },
+ },
+ });
+ ```
+
+- 31bfc44: Extension data references can now be defined in a way that encapsulates the ID string in the type, in addition to the data type itself. The old way of creating extension data references is deprecated and will be removed in a future release.
+
+ For example, the following code:
+
+ ```ts
+ export const myExtension =
+ createExtensionDataRef('my-plugin.my-data');
+ ```
+
+ Should be updated to the following:
+
+ ```ts
+ export const myExtension = createExtensionDataRef().with({
+ id: 'my-plugin.my-data',
+ });
+ ```
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-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.1.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/frontend-app-api@0.7.4-next.0
+ - @backstage/test-utils@1.5.9-next.0
+ - @backstage/types@1.1.1
+
+## @backstage/integration-react@1.1.30-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+
+## @backstage/repo-tools@0.9.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/cli-node@0.2.7
+ - @backstage/errors@1.2.4
+
+## @techdocs/cli@1.8.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.4.2-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/config@1.2.0
+ - @backstage/plugin-techdocs-node@1.12.9-next.0
+
+## @backstage/test-utils@1.5.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/theme@0.5.6
+ - @backstage/types@1.1.1
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-react@0.4.24
+
+## @backstage/plugin-api-docs@0.11.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/plugin-catalog@1.21.2-next.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-react@0.4.24
+
+## @backstage/plugin-app-backend@0.3.72-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-app-node@0.1.23-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-app-node@0.1.23-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+
+## @backstage/plugin-app-visualizer@0.1.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-plugin-api@1.9.3
+
+## @backstage/plugin-auth-backend@0.22.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.0
+ - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.0
+ - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.0
+ - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.0
+ - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.0
+ - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0
+ - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.0
+ - @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.0
+ - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.0
+ - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.0
+ - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.0
+ - @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.0
+ - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-backend@0.22.10-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.0
+
+### Patch Changes
+
+- 39f36a9: Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes.
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-backend@0.22.10-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+
+## @backstage/plugin-auth-node@0.4.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+
+## @backstage/plugin-auth-react@0.1.5-next.0
+
+### Patch Changes
+
+- aeac3e9: feat: Hide visibility of CookieAuthRedirect
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+
+## @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+
+## @backstage/plugin-catalog@1.21.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-react@0.4.24
+ - @backstage/plugin-scaffolder-common@1.5.4
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-catalog-backend@1.24.1-next.0
+
+### Patch Changes
+
+- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-openapi-utils@0.1.16-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0
+
+## @backstage/plugin-catalog-backend-module-aws@0.3.18-next.0
+
+### Patch Changes
+
+- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail.
+- 90a7340: `AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId`
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/integration-aws-node@0.1.12
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-kubernetes-common@0.8.1
+
+## @backstage/plugin-catalog-backend-module-azure@0.1.43-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-catalog-common@1.0.25
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-openapi-utils@0.1.16-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.37-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+
+## @backstage/plugin-catalog-backend-module-gcp@0.1.24-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-kubernetes-common@0.8.1
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.1.40-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+
+## @backstage/plugin-catalog-backend-module-github@0.6.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-catalog-backend-module-github-org@0.1.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-catalog-backend-module-github@0.6.6-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-permission-common@0.8.0
+
+## @backstage/plugin-catalog-backend-module-ldap@0.7.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-catalog-common@1.0.25
+
+## @backstage/plugin-catalog-backend-module-logs@0.0.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.5.31-next.0
+
+### Patch Changes
+
+- 58dff4d: Added option to ingest groups based on their group membership in Azure Entra ID
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-catalog-common@1.0.25
+
+## @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-catalog-common@1.0.25
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.1.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-scaffolder-common@1.5.4
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.3
+ - @backstage/plugin-permission-common@0.8.0
+
+## @backstage/plugin-catalog-graph@0.4.8-next.0
+
+### Patch Changes
+
+- fba7537: Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/types@1.1.1
+
+## @backstage/plugin-catalog-import@0.12.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/plugin-catalog-common@1.0.25
+
+## @backstage/plugin-catalog-node@1.12.5-next.0
+
+### Patch Changes
+
+- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
+## @backstage/plugin-catalog-react@1.12.3-next.0
+
+### Patch Changes
+
+- 31bfc44: Updated alpha definitions of extension data references.
+- 9b89b82: Internal refactor to remove unnecessary `routable` prop in the implementation of the `createEntityContentExtension` alpha export.
+- bebd569: Fix extra divider displayed on user list picker component
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.8
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-react@0.4.24
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+
+## @backstage/plugin-config-schema@0.1.58-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+
+## @backstage/plugin-devtools@0.1.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-devtools-common@0.1.11
+ - @backstage/plugin-permission-react@0.4.24
+
+## @backstage/plugin-devtools-backend@0.3.9-next.0
+
+### Patch Changes
+
+- 32a38e1: Removed unused code for lockfile analysis.
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @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.11
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
+## @backstage/plugin-events-backend@0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.3.9-next.0
+
+### Patch Changes
+
+- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail.
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/config@1.2.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-backend-module-azure@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-backend-module-github@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-backend-module-gitlab@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-backend-test-utils@0.1.33-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-events-node@0.3.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+
+## @backstage/plugin-home@0.7.8-next.0
+
+### Patch Changes
+
+- 31bfc44: Updated alpha definitions of extension data references.
+- fdcc059: Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-home-react@0.1.16-next.0
+
+## @backstage/plugin-home-react@0.1.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-plugin-api@1.9.3
+
+## @backstage/plugin-kubernetes@0.11.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/plugin-kubernetes-common@0.8.1
+ - @backstage/plugin-kubernetes-react@0.4.2-next.0
+
+## @backstage/plugin-kubernetes-backend@0.18.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/integration-aws-node@0.1.12
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-kubernetes-common@0.8.1
+ - @backstage/plugin-kubernetes-node@0.1.17-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
+## @backstage/plugin-kubernetes-cluster@0.0.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/plugin-kubernetes-common@0.8.1
+ - @backstage/plugin-kubernetes-react@0.4.2-next.0
+
+## @backstage/plugin-kubernetes-node@0.1.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-kubernetes-common@0.8.1
+
+## @backstage/plugin-kubernetes-react@0.4.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-kubernetes-common@0.8.1
+
+## @backstage/plugin-notifications@0.2.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-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.4
+
+## @backstage/plugin-notifications-backend@0.3.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-notifications-common@0.0.5
+ - @backstage/plugin-notifications-node@0.2.4-next.0
+ - @backstage/plugin-signals-node@0.1.9-next.0
+
+## @backstage/plugin-notifications-backend-module-email@0.1.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/integration-aws-node@0.1.12
+ - @backstage/types@1.1.1
+ - @backstage/plugin-notifications-common@0.0.5
+ - @backstage/plugin-notifications-node@0.2.4-next.0
+
+## @backstage/plugin-notifications-node@0.2.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/plugin-notifications-common@0.0.5
+ - @backstage/plugin-signals-node@0.1.9-next.0
+
+## @backstage/plugin-org@0.6.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/plugin-catalog-common@1.0.25
+
+## @backstage/plugin-org-react@0.1.27-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-plugin-api@1.9.3
+
+## @backstage/plugin-permission-backend@0.5.47-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
+## @backstage/plugin-permission-node@0.8.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-permission-common@0.8.0
+
+## @backstage/plugin-proxy-backend@0.5.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/types@1.1.1
+
+## @backstage/plugin-scaffolder@1.23.1-next.0
+
+### Patch Changes
+
+- bbd9f56: Cleaned up codebase of RepoUrlPicker
+- b8600fe: Fix issue with `RepoUrlPicker` not refreshing the credentials for a different host
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/plugin-scaffolder-react@1.11.0-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-react@0.4.24
+ - @backstage/plugin-scaffolder-common@1.5.4
+
+## @backstage/plugin-scaffolder-backend@1.23.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.0
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0
+ - @backstage/plugin-scaffolder-common@1.5.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.47-next.0
+
+### Patch Changes
+
+- dae85df: Add examples for `fetch:cookiecutter` scaffolder action & improve related tests
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0
+
+### Patch Changes
+
+- fad1b90: Allow the `createGitlabProjectVariableAction` to use oauth tokens
+
+- aab708e: Added test cases for gitlab:issue:edit examples
+
+- ef742dc: Added test cases for gitlab:projectAccessToken:create example
+
+- 1ba4c2f: Added test cases for gitlab:pipeline:trigger examples
+
+- a6603e4: Add custom action for merge request: **auto**
+
+ The **Auto** action selects the committed action between _create_ and _update_.
+
+ The **Auto** action fetches files using the **/projects/repository/tree endpoint**.
+ After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**.
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.0.6-next.0
+
+### Patch Changes
+
+- 6fc03c7: Add examples for notification:send scaffolder action & improve related tests
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/plugin-notifications-common@0.0.5
+ - @backstage/plugin-notifications-node@0.2.4-next.0
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.0
+
+### Patch Changes
+
+- 449def7: Add examples for fetch:rails scaffolder action & improve related tests
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.1.31-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+ - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.0
+
+## @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-scaffolder-common@1.5.4
+
+## @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-test-utils@0.4.5-next.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+
+## @backstage/plugin-search@1.4.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @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.13
+
+## @backstage/plugin-search-backend@1.5.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-defaults@0.4.2-next.0
+ - @backstage/backend-openapi-utils@0.1.16-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-search-backend-module-catalog@0.1.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/integration-aws-node@0.1.12
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-search-backend-module-explore@0.1.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-search-backend-module-pg@0.5.33-next.0
+
+### Patch Changes
+
+- 7251567: Removing `@backstage/backend-app-api` dependency
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+ - @backstage/plugin-techdocs-node@1.12.9-next.0
+
+## @backstage/plugin-search-backend-node@1.2.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-search-react@1.7.14-next.0
+
+### Patch Changes
+
+- 31bfc44: Updated alpha definitions of extension data references.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-components@0.14.10-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.13
+
+## @backstage/plugin-signals@0.0.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-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.4
+
+## @backstage/plugin-signals-backend@0.1.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-signals-node@0.1.9-next.0
+
+## @backstage/plugin-signals-node@0.1.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
+## @backstage/plugin-techdocs@1.10.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/plugin-auth-react@0.1.5-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/errors@1.2.4
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-search-common@1.2.13
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+
+## @backstage/plugin-techdocs-addons-test-utils@1.0.36-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/test-utils@1.5.9-next.0
+ - @backstage/plugin-catalog@1.21.2-next.0
+ - @backstage/plugin-techdocs@1.10.8-next.0
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+
+## @backstage/plugin-techdocs-backend@1.10.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0
+ - @backstage/plugin-techdocs-node@1.12.9-next.0
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+
+## @backstage/plugin-techdocs-node@1.12.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/integration-aws-node@0.1.12
+ - @backstage/plugin-search-common@1.2.13
+
+## @backstage/plugin-techdocs-react@1.2.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/catalog-model@1.5.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.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/core-compat-api@0.2.8-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-signals-react@0.0.4
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## @backstage/plugin-user-settings-backend@0.2.22-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-signals-node@0.1.9-next.0
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## example-app@0.2.100-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.27.0-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/plugin-home@0.7.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/plugin-scaffolder-react@1.11.0-next.0
+ - @backstage/frontend-app-api@0.7.4-next.0
+ - @backstage/plugin-catalog-graph@0.4.8-next.0
+ - @backstage/plugin-scaffolder@1.23.1-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/plugin-auth-react@0.1.5-next.0
+ - @backstage/app-defaults@1.5.9-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-api-docs@0.11.8-next.0
+ - @backstage/plugin-catalog@1.21.2-next.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-catalog-import@0.12.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0
+ - @backstage/plugin-devtools@0.1.17-next.0
+ - @backstage/plugin-kubernetes@0.11.13-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.14-next.0
+ - @backstage/plugin-notifications@0.2.4-next.0
+ - @backstage/plugin-org@0.6.28-next.0
+ - @backstage/plugin-permission-react@0.4.24
+ - @backstage/plugin-search@1.4.15-next.0
+ - @backstage/plugin-search-common@1.2.13
+ - @backstage/plugin-signals@0.0.9-next.0
+ - @backstage/plugin-techdocs@1.10.8-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+ - @backstage/plugin-user-settings@0.8.10-next.0
+
+## example-app-next@0.0.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.27.0-next.0
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/plugin-home@0.7.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/plugin-scaffolder-react@1.11.0-next.0
+ - @backstage/frontend-app-api@0.7.4-next.0
+ - @backstage/plugin-catalog-graph@0.4.8-next.0
+ - @backstage/plugin-scaffolder@1.23.1-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/plugin-auth-react@0.1.5-next.0
+ - @backstage/app-defaults@1.5.9-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-api-docs@0.11.8-next.0
+ - @backstage/plugin-app-visualizer@0.1.9-next.0
+ - @backstage/plugin-catalog@1.21.2-next.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-catalog-import@0.12.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0
+ - @backstage/plugin-kubernetes@0.11.13-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.14-next.0
+ - @backstage/plugin-notifications@0.2.4-next.0
+ - @backstage/plugin-org@0.6.28-next.0
+ - @backstage/plugin-permission-react@0.4.24
+ - @backstage/plugin-search@1.4.15-next.0
+ - @backstage/plugin-search-common@1.2.13
+ - @backstage/plugin-signals@0.0.9-next.0
+ - @backstage/plugin-techdocs@1.10.8-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+ - @backstage/plugin-user-settings@0.8.10-next.0
+
+## app-next-example-plugin@0.0.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+
+## example-backend@0.0.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.4.2-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/plugin-devtools-backend@0.3.9-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/plugin-app-backend@0.3.72-next.0
+ - @backstage/plugin-auth-backend@0.22.10-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0
+ - @backstage/plugin-kubernetes-backend@0.18.4-next.0
+ - @backstage/plugin-notifications-backend@0.3.4-next.0
+ - @backstage/plugin-permission-backend@0.5.47-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-proxy-backend@0.5.4-next.0
+ - @backstage/plugin-scaffolder-backend@1.23.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0
+ - @backstage/plugin-search-backend@1.5.15-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-explore@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-signals-backend@0.1.9-next.0
+ - @backstage/plugin-techdocs-backend@1.10.10-next.0
+
+## example-backend-legacy@0.2.101-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.0
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/plugin-devtools-backend@0.3.9-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/plugin-search-backend-module-pg@0.5.33-next.0
+ - example-app@0.2.100-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-app-backend@0.3.72-next.0
+ - @backstage/plugin-auth-backend@0.22.10-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0
+ - @backstage/plugin-events-backend@0.3.10-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-kubernetes-backend@0.18.4-next.0
+ - @backstage/plugin-permission-backend@0.5.47-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-proxy-backend@0.5.4-next.0
+ - @backstage/plugin-scaffolder-backend@1.23.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.0
+ - @backstage/plugin-search-backend@1.5.15-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.0
+ - @backstage/plugin-search-backend-module-explore@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-signals-backend@0.1.9-next.0
+ - @backstage/plugin-signals-node@0.1.9-next.0
+ - @backstage/plugin-techdocs-backend@1.10.10-next.0
+
+## e2e-test@0.2.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.18-next.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/errors@1.2.4
+
+## techdocs-cli-embedded-app@0.2.99-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.27.0-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/app-defaults@1.5.9-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/test-utils@1.5.9-next.0
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-catalog@1.21.2-next.0
+ - @backstage/plugin-techdocs@1.10.8-next.0
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+
+## @internal/plugin-todo-list@1.0.30-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-plugin-api@1.9.3
+
+## @internal/plugin-todo-list-backend@1.0.30-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/errors@1.2.4
+ - @backstage/plugin-auth-node@0.4.18-next.0
diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md
index 234d0d04b7..a6652bd036 100644
--- a/docs/tooling/cli/03-commands.md
+++ b/docs/tooling/cli/03-commands.md
@@ -26,7 +26,6 @@ repo [command] Command that run across an entire
package [command] Lifecycle scripts for individual packages
migrate [command] Migration utilities
versions:bump [options] Bump Backstage packages to the latest versions
-versions:check [options] Check Backstage package versioning
clean Delete cache directories [DEPRECATED]
build-workspace [packages...] Builds a temporary dist workspace from the provided
packages
@@ -327,8 +326,7 @@ Options:
## versions\:bump
Bump all `@backstage` packages to the latest versions. This checks for updates
-in the package registry, and will update entries both in `yarn.lock` and
-`package.json` files when necessary.
+in the package registry, and will update entries `package.json` files when necessary.
```text
Usage: backstage-cli versions:bump [options]
@@ -339,23 +337,6 @@ Options:
--release Bump to a specific Backstage release line or version (default: "main")
```
-## versions\:check
-
-Validate `@backstage` dependencies within the repo, making sure that there are
-no duplicates of packages that might lead to breakages.
-
-By supplying the `--fix` flag the command will attempt to fix any conflict that
-can be resolved by editing `yarn.lock`, but will not attempt to search for
-remote updates or modify any `package.json` files.
-
-```text
-Usage: backstage-cli versions:check [options]
-
-Options:
- --fix Fix any auto-fixable versioning problems
- -h, --help display help for command
-```
-
## build-workspace
Builds a mirror of the workspace using the packaged production version of each
diff --git a/docs/tutorials/enable-public-entry.md b/docs/tutorials/enable-public-entry.md
index 6282f66418..25d7382f13 100644
--- a/docs/tutorials/enable-public-entry.md
+++ b/docs/tutorials/enable-public-entry.md
@@ -100,3 +100,47 @@ With that, Backstage's cli and backend will detect public entry point and serve
5. Finally, as soon as you log in, you will be redirected to the main app home page (inspect the page and see that the protected bundle was served from the app backend after the redirect).
That's it!
+
+## New Frontend System
+
+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';
+
+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],
+ }),
+ ],
+});
+
+ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot());
+```
diff --git a/microsite/data/plugins/dora-metrics.yaml b/microsite/data/plugins/dora-metrics.yaml
deleted file mode 100644
index 3f59902f46..0000000000
--- a/microsite/data/plugins/dora-metrics.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
----
-title: DORA Metrics
-author: Okay
-authorUrl: https://www.okayhq.com
-category: Metrics
-description: Embed dashboards (like DORA metrics) in your team or service pages from any dev tools (including Github, Gitlab, Jira, Argo, CircleCI, Buildkite, Pagerduty, Rollbar, Sentry, etc).
-documentation: https://github.com/OkayHQ/backstage-plugin
-iconUrl: /img/okay.png
-npmPackageName: '@okayhq/backstage-plugin'
-addedDate: '2022-06-16'
diff --git a/microsite/src/pages/community/index.tsx b/microsite/src/pages/community/index.tsx
index c331638c78..a32f5edd13 100644
--- a/microsite/src/pages/community/index.tsx
+++ b/microsite/src/pages/community/index.tsx
@@ -96,6 +96,11 @@ const Community = () => {
url: 'https://tanzu.vmware.com/developer-portal',
logo: 'img/partner-logo-tanzubybroadcom.png',
},
+ {
+ name: 'StatusNeo',
+ url: 'https://statusneo.com/backstage',
+ logo: 'img/partner-logo-statusneo.png',
+ },
];
//#endregion
diff --git a/microsite/static/img/okay.png b/microsite/static/img/okay.png
deleted file mode 100644
index 357219d9d2..0000000000
Binary files a/microsite/static/img/okay.png and /dev/null differ
diff --git a/microsite/static/img/partner-logo-statusneo.png b/microsite/static/img/partner-logo-statusneo.png
new file mode 100644
index 0000000000..61a676a220
Binary files /dev/null and b/microsite/static/img/partner-logo-statusneo.png differ
diff --git a/mkdocs.yml b/mkdocs.yml
index f163005377..8527d68c28 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -20,7 +20,7 @@ nav:
- Support and community: 'overview/support.md'
- Logo assets: 'overview/logos.md'
- Getting Started:
- - Installing a standalone server: 'getting-started/index.md'
+ - Creating your Backstage App: 'getting-started/index.md'
- Configuring Backstage:
- Database: 'getting-started/config/database.md'
- Authentication: 'getting-started/config/authentication.md'
diff --git a/package.json b/package.json
index 07c9b2917b..73d0ca5b19 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "root",
- "version": "1.29.0",
+ "version": "1.30.0-next.0",
"private": true,
"repository": {
"type": "git",
diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md
index a03034112c..a045f9a4f9 100644
--- a/packages/app-defaults/CHANGELOG.md
+++ b/packages/app-defaults/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/app-defaults
+## 1.5.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-permission-react@0.4.24
+
## 1.5.8
### Patch Changes
diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json
index 8997ba860f..2389c2cddc 100644
--- a/packages/app-defaults/package.json
+++ b/packages/app-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/app-defaults",
- "version": "1.5.8",
+ "version": "1.5.9-next.0",
"description": "Provides the default wiring of a Backstage App",
"backstage": {
"role": "web-library"
diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md
index fb61388f22..f05b81642f 100644
--- a/packages/app-next-example-plugin/CHANGELOG.md
+++ b/packages/app-next-example-plugin/CHANGELOG.md
@@ -1,5 +1,13 @@
# app-next-example-plugin
+## 0.0.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+
## 0.0.13
### Patch Changes
diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json
index c48d5a92be..d397883063 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.13",
+ "version": "0.0.14-next.0",
"description": "Backstage internal example plugin",
"backstage": {
"role": "frontend-plugin",
diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md
index 3bbf698ebd..794484d2ce 100644
--- a/packages/app-next/CHANGELOG.md
+++ b/packages/app-next/CHANGELOG.md
@@ -1,5 +1,48 @@
# example-app-next
+## 0.0.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.27.0-next.0
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/plugin-home@0.7.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/plugin-scaffolder-react@1.11.0-next.0
+ - @backstage/frontend-app-api@0.7.4-next.0
+ - @backstage/plugin-catalog-graph@0.4.8-next.0
+ - @backstage/plugin-scaffolder@1.23.1-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/plugin-auth-react@0.1.5-next.0
+ - @backstage/app-defaults@1.5.9-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-compat-api@0.2.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-api-docs@0.11.8-next.0
+ - @backstage/plugin-app-visualizer@0.1.9-next.0
+ - @backstage/plugin-catalog@1.21.2-next.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-catalog-import@0.12.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0
+ - @backstage/plugin-kubernetes@0.11.13-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.14-next.0
+ - @backstage/plugin-notifications@0.2.4-next.0
+ - @backstage/plugin-org@0.6.28-next.0
+ - @backstage/plugin-permission-react@0.4.24
+ - @backstage/plugin-search@1.4.15-next.0
+ - @backstage/plugin-search-common@1.2.13
+ - @backstage/plugin-signals@0.0.9-next.0
+ - @backstage/plugin-techdocs@1.10.8-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+ - @backstage/plugin-user-settings@0.8.10-next.0
+
## 0.0.13
### Patch Changes
diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml
index 271040ecec..f6d0174ca1 100644
--- a/packages/app-next/app-config.yaml
+++ b/packages/app-next/app-config.yaml
@@ -43,6 +43,8 @@ app:
- entity-card:org/ownership
- entity-card:org/user-profile
+ - entity-content:kubernetes/kubernetes
+
# scmAuthExtension: >-
# createScmAuthExtension({
# id: 'apis.scmAuth.addons.ghe',
diff --git a/packages/app-next/package.json b/packages/app-next/package.json
index c0c19bc40c..b4b9f69c51 100644
--- a/packages/app-next/package.json
+++ b/packages/app-next/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app-next",
- "version": "0.0.13",
+ "version": "0.0.14-next.0",
"private": true,
"repository": {
"type": "git",
diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx
index c911a01258..b57c680474 100644
--- a/packages/app-next/src/App.tsx
+++ b/packages/app-next/src/App.tsx
@@ -36,18 +36,14 @@ import { convertLegacyApp } from '@backstage/core-compat-api';
import { FlatRoutes } from '@backstage/core-app-api';
import { Route } from 'react-router';
import { CatalogImportPage } from '@backstage/plugin-catalog-import';
-import {
- createApiFactory,
- configApiRef,
- SignInPageProps,
-} from '@backstage/core-plugin-api';
+import { createApiFactory, configApiRef } from '@backstage/core-plugin-api';
import {
ScmAuth,
ScmIntegrationsApi,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
-import { createSignInPageExtension } from '@backstage/frontend-plugin-api';
-import { SignInPage } from '@backstage/core-components';
+import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha';
+import { signInPageOverrides } from './overrides/SignInPage';
/*
@@ -90,12 +86,6 @@ const homePageExtension = createExtension({
},
});
-const signInPage = createSignInPageExtension({
- name: 'guest',
- loader: async () => (props: SignInPageProps) =>
- ,
-});
-
const scmAuthExtension = createApiExtension({
factory: ScmAuth.createDefaultApiFactory(),
});
@@ -121,13 +111,14 @@ const app = createApp({
userSettingsPlugin,
homePlugin,
appVisualizerPlugin,
+ kubernetesPlugin,
+ signInPageOverrides,
...collectedLegacyPlugins,
createExtensionOverrides({
extensions: [
homePageExtension,
scmAuthExtension,
scmIntegrationApi,
- signInPage,
notFoundErrorPage,
],
}),
diff --git a/packages/app-next/src/index-public-experimental.tsx b/packages/app-next/src/index-public-experimental.tsx
new file mode 100644
index 0000000000..56bc698ffd
--- /dev/null
+++ b/packages/app-next/src/index-public-experimental.tsx
@@ -0,0 +1,49 @@
+/*
+ * 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 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';
+
+const authRedirectExtension = createExtension({
+ namespace: 'app',
+ name: 'layout',
+ attachTo: { id: 'app/root', input: 'children' },
+ output: {
+ element: coreExtensionData.reactElement,
+ },
+ factory: () => ({
+ element: ,
+ }),
+});
+
+const app = createApp({
+ features: [
+ signInPageOverrides,
+ createExtensionOverrides({
+ extensions: [authRedirectExtension],
+ }),
+ ],
+});
+
+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
new file mode 100644
index 0000000000..364a749293
--- /dev/null
+++ b/packages/app-next/src/overrides/SignInPage.tsx
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { SignInPage } from '@backstage/core-components';
+import {
+ createExtensionOverrides,
+ createSignInPageExtension,
+} from '@backstage/frontend-plugin-api';
+
+const signInPage = createSignInPageExtension({
+ name: 'guest',
+ loader: async () => props => ,
+});
+
+export const signInPageOverrides = createExtensionOverrides({
+ extensions: [signInPage],
+});
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index a75cf24a07..4ba5430905 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,46 @@
# example-app
+## 0.2.100-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.27.0-next.0
+ - @backstage/plugin-catalog-react@1.12.3-next.0
+ - @backstage/plugin-search-react@1.7.14-next.0
+ - @backstage/plugin-home@0.7.8-next.0
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/plugin-scaffolder-react@1.11.0-next.0
+ - @backstage/frontend-app-api@0.7.4-next.0
+ - @backstage/plugin-catalog-graph@0.4.8-next.0
+ - @backstage/plugin-scaffolder@1.23.1-next.0
+ - @backstage/core-app-api@1.14.1-next.0
+ - @backstage/plugin-auth-react@0.1.5-next.0
+ - @backstage/app-defaults@1.5.9-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/integration-react@1.1.30-next.0
+ - @backstage/theme@0.5.6
+ - @backstage/plugin-api-docs@0.11.8-next.0
+ - @backstage/plugin-catalog@1.21.2-next.0
+ - @backstage/plugin-catalog-common@1.0.25
+ - @backstage/plugin-catalog-import@0.12.2-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0
+ - @backstage/plugin-devtools@0.1.17-next.0
+ - @backstage/plugin-kubernetes@0.11.13-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.14-next.0
+ - @backstage/plugin-notifications@0.2.4-next.0
+ - @backstage/plugin-org@0.6.28-next.0
+ - @backstage/plugin-permission-react@0.4.24
+ - @backstage/plugin-search@1.4.15-next.0
+ - @backstage/plugin-search-common@1.2.13
+ - @backstage/plugin-signals@0.0.9-next.0
+ - @backstage/plugin-techdocs@1.10.8-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0
+ - @backstage/plugin-techdocs-react@1.2.7-next.0
+ - @backstage/plugin-user-settings@0.8.10-next.0
+
## 0.2.99
### Patch Changes
diff --git a/packages/app/package.json b/packages/app/package.json
index 29cfbf0357..0468a2f7a8 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app",
- "version": "0.2.99",
+ "version": "0.2.100-next.0",
"backstage": {
"role": "frontend"
},
diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md
index 70a163ee34..94ab9afb6f 100644
--- a/packages/backend-app-api/CHANGELOG.md
+++ b/packages/backend-app-api/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/backend-app-api
+## 0.8.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/cli-node@0.2.7
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
## 0.8.0
### Minor Changes
diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json
index 8bd156c3a4..9372b837e0 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.8.0",
+ "version": "0.8.1-next.0",
"description": "Core API used by Backstage backend apps",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md
index 6d3218e0ed..01499c5d92 100644
--- a/packages/backend-common/CHANGELOG.md
+++ b/packages/backend-common/CHANGELOG.md
@@ -1,5 +1,33 @@
# @backstage/backend-common
+## 0.23.4-next.0
+
+### Patch Changes
+
+- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail.
+- 7e13b7a: The remaining exports in the package have now been deprecated:
+
+ - `cacheToPluginCacheManager`
+ - `createLegacyAuthAdapters`
+ - `LegacyCreateRouter`
+ - `legacyPlugin`
+ - `loggerToWinstonLogger`
+ - `makeLegacyPlugin`
+
+ Users of these export should fully [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating).
+
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-dev-utils@0.1.4
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @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-auth-node@0.4.18-next.0
+
## 0.23.3
### Patch Changes
diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json
index 553c800ea5..4ace5e950a 100644
--- a/packages/backend-common/package.json
+++ b/packages/backend-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-common",
- "version": "0.23.3",
+ "version": "0.23.4-next.0",
"description": "Common functionality library for Backstage backends",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md
index 7d2ad7d6b7..a8cc588f1e 100644
--- a/packages/backend-defaults/CHANGELOG.md
+++ b/packages/backend-defaults/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/backend-defaults
+## 0.4.2-next.0
+
+### Patch Changes
+
+- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`.
+- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens.
+- 8e967da: Fixed the routing of the new health check service, the health endpoints should now properly be available at `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness`.
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-app-api@0.8.1-next.0
+ - @backstage/backend-dev-utils@0.1.4
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @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-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+
## 0.4.0
### Minor Changes
diff --git a/packages/backend-defaults/api-report-rootHttpRouter.md b/packages/backend-defaults/api-report-rootHttpRouter.md
index 329398a7b8..36f3aa3ab6 100644
--- a/packages/backend-defaults/api-report-rootHttpRouter.md
+++ b/packages/backend-defaults/api-report-rootHttpRouter.md
@@ -3,6 +3,7 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
+///
///
import { Config } from '@backstage/config';
@@ -17,10 +18,17 @@ import { LoggerService } from '@backstage/backend-plugin-api';
import { RequestHandler } from 'express';
import { RequestListener } from 'http';
import { RootConfigService } from '@backstage/backend-plugin-api';
+import { RootHealthService } from '@backstage/backend-plugin-api';
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
+import { Router } from 'express';
import type { Server } from 'node:http';
import { ServiceFactory } from '@backstage/backend-plugin-api';
+// @public (undocumented)
+export function createHealthRouter(options: {
+ health: RootHealthService;
+}): Router;
+
// @public
export function createHttpServer(
listener: RequestListener,
diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json
index 87da6e0017..9c04507003 100644
--- a/packages/backend-defaults/package.json
+++ b/packages/backend-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-defaults",
- "version": "0.4.0",
+ "version": "0.4.2-next.0",
"description": "Backend defaults used by Backstage backend apps",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts
index 007d1dd9cd..3772ab0fab 100644
--- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts
+++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts
@@ -1,5 +1,3 @@
-import { RootHealthService } from '@backstage/backend-plugin-api';
-
/*
* Copyright 2024 The Backstage Authors
*
@@ -16,9 +14,13 @@ import { RootHealthService } from '@backstage/backend-plugin-api';
* limitations under the License.
*/
+import { RootHealthService } from '@backstage/backend-plugin-api';
import Router from 'express-promise-router';
import { Request, Response } from 'express';
+/**
+ * @public
+ */
export function createHealthRouter(options: { health: RootHealthService }) {
const router = Router();
diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts
index 9c2d96dab2..e9aee8a0e0 100644
--- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts
+++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts
@@ -18,6 +18,7 @@ export {
DefaultRootHttpRouter,
type DefaultRootHttpRouterOptions,
} from './DefaultRootHttpRouter';
+export { createHealthRouter } from './createHealthRouter';
export * from './http';
export {
rootHttpRouterServiceFactory,
diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts
new file mode 100644
index 0000000000..dea0159dff
--- /dev/null
+++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ServiceFactoryTester,
+ mockServices,
+} from '@backstage/backend-test-utils';
+import { Express } from 'express';
+import request from 'supertest';
+import { rootHttpRouterServiceFactory } from './rootHttpRouterServiceFactory';
+import { coreServices } from '@backstage/backend-plugin-api';
+
+describe('rootHttpRouterServiceFactory', () => {
+ it('should make the health endpoints available', async () => {
+ let app: Express | undefined = undefined;
+
+ const tester = ServiceFactoryTester.from(
+ rootHttpRouterServiceFactory({
+ configure(options) {
+ options.applyDefaults();
+ app = options.app;
+ },
+ }),
+ {
+ dependencies: [
+ mockServices.rootConfig.factory({
+ data: {
+ app: { baseUrl: 'http://localhost' },
+ backend: {
+ baseUrl: 'http://localhost',
+ listen: { host: '', port: 0 },
+ },
+ },
+ }),
+ ],
+ },
+ );
+
+ // Trigger creation of the http service, accessing the app instance through the configure callback
+ await tester.getSubject();
+
+ await request(app!)
+ .get('/.backstage/health/v1/liveness')
+ .expect(200, { status: 'ok' });
+
+ await request(app!).get('/.backstage/health/v1/readiness').expect(503, {
+ message: 'Backend has not started yet',
+ status: 'error',
+ });
+
+ const lifecycle = await tester.getService(coreServices.rootLifecycle);
+
+ await (lifecycle as any).startup(); // Trigger startup by calling the private startup method
+
+ await request(app!).get('/.backstage/health/v1/readiness').expect(200, {
+ status: 'ok',
+ });
+
+ expect('test').toBe('test');
+ });
+});
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts
index 7f02f1468f..5a2655ad1c 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts
@@ -213,6 +213,7 @@ export class AwsCodeCommitUrlReader implements UrlReaderService {
);
const codeCommit = new CodeCommitClient({
+ customUserAgent: 'backstage-aws-codecommit-url-reader',
region: region,
credentials: credentials,
});
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts
index 27f60f5b77..324538add4 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts
@@ -222,6 +222,7 @@ export class AwsS3UrlReader implements UrlReaderService {
);
const s3 = new S3Client({
+ customUserAgent: 'backstage-aws-s3-url-reader',
region: region,
credentials: credentials,
endpoint: integration.config.endpoint,
diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md
index 3003a8e177..ade7a7c0ec 100644
--- a/packages/backend-dynamic-feature-service/CHANGELOG.md
+++ b/packages/backend-dynamic-feature-service/CHANGELOG.md
@@ -1,5 +1,31 @@
# @backstage/backend-dynamic-feature-service
+## 0.2.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/backend-app-api@0.8.1-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/cli-node@0.2.7
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-app-node@0.1.23-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-backend@0.3.10-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-scaffolder-node@0.4.9-next.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-search-common@1.2.13
+
## 0.2.15
### Patch Changes
diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json
index 0d05b97bd9..cc7a8b390b 100644
--- a/packages/backend-dynamic-feature-service/package.json
+++ b/packages/backend-dynamic-feature-service/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-dynamic-feature-service",
"description": "Backstage dynamic feature service",
- "version": "0.2.15",
+ "version": "0.2.16-next.0",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md
index 04538ca02e..5acae66b6a 100644
--- a/packages/backend-legacy/CHANGELOG.md
+++ b/packages/backend-legacy/CHANGELOG.md
@@ -1,5 +1,47 @@
# example-backend-legacy
+## 0.2.101-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.0
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/plugin-catalog-node@1.12.5-next.0
+ - @backstage/plugin-devtools-backend@0.3.9-next.0
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/plugin-search-backend-module-pg@0.5.33-next.0
+ - example-app@0.2.100-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-client@1.6.5
+ - @backstage/catalog-model@1.5.0
+ - @backstage/config@1.2.0
+ - @backstage/plugin-app-backend@0.3.72-next.0
+ - @backstage/plugin-auth-backend@0.22.10-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0
+ - @backstage/plugin-events-backend@0.3.10-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+ - @backstage/plugin-kubernetes-backend@0.18.4-next.0
+ - @backstage/plugin-permission-backend@0.5.47-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-proxy-backend@0.5.4-next.0
+ - @backstage/plugin-scaffolder-backend@1.23.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.0
+ - @backstage/plugin-search-backend@1.5.15-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.0
+ - @backstage/plugin-search-backend-module-explore@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-signals-backend@0.1.9-next.0
+ - @backstage/plugin-signals-node@0.1.9-next.0
+ - @backstage/plugin-techdocs-backend@1.10.10-next.0
+
## 0.2.100
### Patch Changes
diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json
index 5861719455..8b07afca38 100644
--- a/packages/backend-legacy/package.json
+++ b/packages/backend-legacy/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend-legacy",
- "version": "0.2.100",
+ "version": "0.2.101-next.0",
"backstage": {
"role": "backend"
},
diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md
index 540a239ad6..1056cc4cc1 100644
--- a/packages/backend-openapi-utils/CHANGELOG.md
+++ b/packages/backend-openapi-utils/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/backend-openapi-utils
+## 0.1.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/errors@1.2.4
+
## 0.1.15
### Patch Changes
diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json
index 3059af5ffe..7b7778f9fe 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.15",
+ "version": "0.1.16-next.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md
index 3f47354561..9dd2e2587c 100644
--- a/packages/backend-plugin-api/CHANGELOG.md
+++ b/packages/backend-plugin-api/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/backend-plugin-api
+## 0.7.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.1.14
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-permission-common@0.8.0
+
## 0.7.0
### Minor Changes
diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json
index 52fbfb5806..4bad9325f1 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.7.0",
+ "version": "0.7.1-next.0",
"description": "Core API used by Backstage backend plugins",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-plugin-api/src/services/definitions/AuthService.ts b/packages/backend-plugin-api/src/services/definitions/AuthService.ts
index dce5e9be3b..1e0cf6e56b 100644
--- a/packages/backend-plugin-api/src/services/definitions/AuthService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/AuthService.ts
@@ -208,7 +208,7 @@ export interface AuthService {
*
* @remarks
*
- * This method should be called before each request. Do not cold on to the
+ * This method should be called before each request. Do not hold on to the
* issued token and reuse it for future calls.
*/
getPluginRequestToken(options: {
diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md
index fef4fd56a9..929a38084c 100644
--- a/packages/backend-tasks/CHANGELOG.md
+++ b/packages/backend-tasks/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/backend-tasks
+## 0.5.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.23.4-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+
## 0.5.27
### Patch Changes
diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json
index 998e1d0650..51e8c4611e 100644
--- a/packages/backend-tasks/package.json
+++ b/packages/backend-tasks/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-tasks",
"description": "Common distributed task management library for Backstage backends",
- "version": "0.5.27",
+ "version": "0.5.28-next.0",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md
index 9bee6fae55..72672bba7f 100644
--- a/packages/backend-test-utils/CHANGELOG.md
+++ b/packages/backend-test-utils/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/backend-test-utils
+## 0.4.5-next.0
+
+### Patch Changes
+
+- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service.
+- Updated dependencies
+ - @backstage/backend-defaults@0.4.2-next.0
+ - @backstage/backend-app-api@0.8.1-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-events-node@0.3.9-next.0
+
## 0.4.4
### Patch Changes
diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json
index 875949c0a0..e562b89c84 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.4.4",
+ "version": "0.4.5-next.0",
"description": "Test helpers library for Backstage backends",
"backstage": {
"role": "node-library"
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 ecba34290d..a01d4bebf5 100644
--- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
+++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
@@ -242,6 +242,15 @@ describe('TestBackend', () => {
expect(res.body).toEqual({ message: 'pong' });
});
+ it('should expose health check endpoints', async () => {
+ const { server } = await startTestBackend({ features: [] });
+
+ const res = await request(server).get('/.backstage/health/v1/liveness');
+
+ expect(res.status).toEqual(200);
+ expect(res.body).toEqual({ status: 'ok' });
+ });
+
it('should provide extension point implementations', async () => {
expect.assertions(3);
diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts
index 16ee6b78d2..3d662a8c12 100644
--- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts
+++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts
@@ -37,6 +37,7 @@ import express from 'express';
// Direct internal import to avoid duplication
// eslint-disable-next-line @backstage/no-forbidden-package-imports
import { InternalBackendFeature } from '@backstage/backend-plugin-api/src/wiring/types';
+import { createHealthRouter } from '@backstage/backend-defaults/rootHttpRouter';
/** @public */
export interface TestBackendOptions {
@@ -73,6 +74,7 @@ export const defaultServiceFactories = [
mockServices.lifecycle.factory(),
mockServices.logger.factory(),
mockServices.permissions.factory(),
+ mockServices.rootHealth.factory(),
mockServices.rootLifecycle.factory(),
mockServices.rootLogger.factory(),
mockServices.scheduler.factory(),
@@ -251,15 +253,18 @@ export async function startTestBackend(
config: coreServices.rootConfig,
lifecycle: coreServices.rootLifecycle,
rootLogger: coreServices.rootLogger,
+ health: coreServices.rootHealth,
},
- async factory({ config, lifecycle, rootLogger }) {
+ async factory({ config, lifecycle, rootLogger, health }) {
const router = DefaultRootHttpRouter.create();
const logger = rootLogger.child({ service: 'rootHttpRouter' });
const app = express();
const middleware = MiddlewareFactory.create({ config, logger });
+ const healthRouter = createHealthRouter({ health });
+ app.use(healthRouter);
app.use(router.handler());
app.use(middleware.notFound());
app.use(middleware.error());
diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md
index 854993119c..6110ab5f99 100644
--- a/packages/backend/CHANGELOG.md
+++ b/packages/backend/CHANGELOG.md
@@ -1,5 +1,42 @@
# example-backend
+## 0.0.29-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.4.2-next.0
+ - @backstage/plugin-catalog-backend@1.24.1-next.0
+ - @backstage/plugin-devtools-backend@0.3.9-next.0
+ - @backstage/backend-plugin-api@0.7.1-next.0
+ - @backstage/backend-tasks@0.5.28-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/plugin-app-backend@0.3.72-next.0
+ - @backstage/plugin-auth-backend@0.22.10-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.0
+ - @backstage/plugin-auth-node@0.4.18-next.0
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0
+ - @backstage/plugin-kubernetes-backend@0.18.4-next.0
+ - @backstage/plugin-notifications-backend@0.3.4-next.0
+ - @backstage/plugin-permission-backend@0.5.47-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.0
+ - @backstage/plugin-permission-common@0.8.0
+ - @backstage/plugin-permission-node@0.8.1-next.0
+ - @backstage/plugin-proxy-backend@0.5.4-next.0
+ - @backstage/plugin-scaffolder-backend@1.23.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0
+ - @backstage/plugin-search-backend@1.5.15-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-explore@0.1.29-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0
+ - @backstage/plugin-search-backend-node@1.2.28-next.0
+ - @backstage/plugin-signals-backend@0.1.9-next.0
+ - @backstage/plugin-techdocs-backend@1.10.10-next.0
+
## 0.0.28
### Patch Changes
diff --git a/packages/backend/package.json b/packages/backend/package.json
index ae47716326..27c378e339 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend",
- "version": "0.0.28",
+ "version": "0.0.29-next.0",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index c062324abb..cbeb8493eb 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,40 @@
# @backstage/cli
+## 0.27.0-next.0
+
+### Minor Changes
+
+- 32a38e1: **BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands.
+
+ The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files.
+
+ The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic.
+
+ The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches.
+
+ The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future.
+
+### Patch Changes
+
+- 7eb08a6: Add frontend-dynamic-container role to eslint config factory
+- fbc7819: Use ES2022 in CLI bundler
+- e53074f: Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used.
+- ee2b0e5: The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag.
+- 239dffc: Remove usage of deprecated functionality from @backstage/config-loader
+- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`.
+- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point.
+- Updated dependencies
+ - @backstage/integration@1.14.0-next.0
+ - @backstage/config-loader@1.8.2-next.0
+ - @backstage/catalog-model@1.5.0
+ - @backstage/cli-common@0.1.14
+ - @backstage/cli-node@0.2.7
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/eslint-plugin@0.1.8
+ - @backstage/release-manifests@0.0.11
+ - @backstage/types@1.1.1
+
## 0.26.11
### Patch Changes
diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md
index 01b0f7ddec..907f0298f2 100644
--- a/packages/cli/cli-report.md
+++ b/packages/cli/cli-report.md
@@ -22,7 +22,6 @@ Commands:
package [command]
migrate [command]
versions:bump [options]
- versions:check [options]
versions:migrate [options]
clean
build-workspace [options] [packages...]
@@ -607,16 +606,6 @@ Options:
-h, --help
```
-### `backstage-cli versions:check`
-
-```
-Usage: backstage-cli versions:check [options]
-
-Options:
- --fix
- -h, --help
-```
-
### `backstage-cli versions:migrate`
```
diff --git a/packages/cli/package.json b/packages/cli/package.json
index ccc4818331..0c999b8b9c 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli",
- "version": "0.26.11",
+ "version": "0.27.0-next.0",
"description": "CLI for developing Backstage plugins and apps",
"backstage": {
"role": "cli"
diff --git a/packages/cli/src/commands/build/buildFrontend.ts b/packages/cli/src/commands/build/buildFrontend.ts
index 62da74e6d3..f29e0bdd38 100644
--- a/packages/cli/src/commands/build/buildFrontend.ts
+++ b/packages/cli/src/commands/build/buildFrontend.ts
@@ -16,11 +16,9 @@
import fs from 'fs-extra';
import { resolve as resolvePath } from 'path';
-import { buildBundle } from '../../lib/bundler';
+import { buildBundle, getModuleFederationOptions } from '../../lib/bundler';
import { getEnvironmentParallelism } from '../../lib/parallel';
import { loadCliConfig } from '../../lib/config';
-import chalk from 'chalk';
-import { BuildOptions } from '../../lib/bundler/types';
interface BuildAppOptions {
targetDir: string;
@@ -29,29 +27,6 @@ interface BuildAppOptions {
isModuleFederationRemote?: true;
}
-function getModuleFederationOptions(
- name: string,
- isRemote?: boolean,
-): BuildOptions['moduleFederation'] {
- if (!isRemote && !process.env.EXPERIMENTAL_MODULE_FEDERATION) {
- return undefined;
- }
-
- console.log(
- chalk.yellow(
- `â ī¸ WARNING: Module federation is experimental and will receive immediate breaking changes in the future.`,
- ),
- );
-
- return {
- mode: isRemote ? 'remote' : 'host',
- // The default output mode requires the name to be a usable as a code
- // symbol, there might be better options here but for now we need to
- // sanitize the name.
- name: name.replaceAll('@', '').replaceAll('/', '__').replaceAll('-', '_'),
- };
-}
-
export async function buildFrontend(options: BuildAppOptions) {
const { targetDir, writeStats, configPaths } = options;
const { name } = await fs.readJson(resolvePath(targetDir, 'package.json'));
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index 25a3a4e26c..fa4bb05333 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -396,7 +396,7 @@ export function registerCommands(program: Command) {
.action(lazy(() => import('./versions/bump').then(m => m.default)));
program
- .command('versions:check')
+ .command('versions:check', { hidden: true })
.option('--fix', 'Fix any auto-fixable versioning problems')
.description('Check Backstage package versioning')
.action(lazy(() => import('./versions/lint').then(m => m.default)));
diff --git a/packages/cli/src/commands/start/command.ts b/packages/cli/src/commands/start/command.ts
index 2f2af95d85..aa46b717bb 100644
--- a/packages/cli/src/commands/start/command.ts
+++ b/packages/cli/src/commands/start/command.ts
@@ -15,6 +15,7 @@
*/
import { OptionValues } from 'commander';
+import { PackageRole } from '@backstage/cli-node';
import { findRoleFromCommand } from '../../lib/role';
import { startBackend, startBackendPlugin } from './startBackend';
import { startFrontend } from './startFrontend';
@@ -47,6 +48,13 @@ export async function command(opts: OptionValues): Promise {
case 'frontend-plugin':
case 'frontend-plugin-module':
return startFrontend({ entry: 'dev/index', ...options });
+ case 'frontend-dynamic-container' as PackageRole: // experimental
+ return startFrontend({
+ entry: 'src/index',
+ ...options,
+ skipOpenBrowser: true,
+ isModuleFederationRemote: true,
+ });
default:
throw new Error(
`Start command is not supported for package role '${role}'`,
diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts
index a809473788..20faf4a9ff 100644
--- a/packages/cli/src/commands/start/startFrontend.ts
+++ b/packages/cli/src/commands/start/startFrontend.ts
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-import { serveBundle } from '../../lib/bundler';
+import { readJson } from 'fs-extra';
+import { getModuleFederationOptions, serveBundle } from '../../lib/bundler';
+import { paths } from '../../lib/paths';
interface StartAppOptions {
verifyVersions?: boolean;
@@ -22,14 +24,23 @@ interface StartAppOptions {
checksEnabled: boolean;
configPaths: string[];
+ skipOpenBrowser?: boolean;
+ isModuleFederationRemote?: boolean;
}
export async function startFrontend(options: StartAppOptions) {
+ const { name } = await readJson(paths.resolveTarget('package.json'));
+
const waitForExit = await serveBundle({
entry: options.entry,
checksEnabled: options.checksEnabled,
configPaths: options.configPaths,
verifyVersions: options.verifyVersions,
+ skipOpenBrowser: options.skipOpenBrowser,
+ moduleFederation: getModuleFederationOptions(
+ name,
+ options.isModuleFederationRemote,
+ ),
});
await waitForExit();
diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts
index b8677a48c5..cc48b91fd8 100644
--- a/packages/cli/src/commands/versions/bump.test.ts
+++ b/packages/cli/src/commands/versions/bump.test.ts
@@ -23,7 +23,6 @@ import { YarnInfoInspectData } from '../../lib/versioning/packages';
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { NotFoundError } from '@backstage/errors';
-import { Lockfile } from '../../lib/versioning/Lockfile';
import {
MockDirectory,
createMockDirectory,
@@ -117,17 +116,6 @@ const lockfileMock = `${HEADER}
version "1.0.3"
`;
-// This is the lockfile that we produce to unlock versions before we run yarn install
-const lockfileMockResult = `${HEADER}
-"@backstage/core@^1.0.5":
- version "1.0.6"
- dependencies:
- "@backstage/core-api" "^1.0.6"
-
-"@backstage/theme@^1.0.0":
- version "1.0.0"
-`;
-
// Avoid flakes by comparing sorted log lines. File system access is async, which leads to the log line order being indeterministic
const expectLogsToMatch = (
recievedLogs: String[],
@@ -204,11 +192,7 @@ describe('bump', () => {
'Using default pattern glob @backstage/*',
'Checking for updates of @backstage/core',
'Checking for updates of @backstage/theme',
- 'Checking for updates of @backstage/core-api',
'Some packages are outdated, updating',
- 'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
- 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
- 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/core in a to ^1.0.6',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/theme in b to ^2.0.0',
@@ -220,9 +204,8 @@ describe('bump', () => {
'Version bump complete!',
]);
- expect(mockFetchPackageInfo).toHaveBeenCalledTimes(3);
+ expect(mockFetchPackageInfo).toHaveBeenCalledTimes(2);
expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core');
- expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core-api');
expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme');
expect(runObj.run).toHaveBeenCalledTimes(1);
@@ -232,12 +215,6 @@ describe('bump', () => {
expect.any(Object),
);
- const lockfileContents = await fs.readFile(
- mockDir.resolve('yarn.lock'),
- 'utf8',
- );
- expect(lockfileContents).toBe(lockfileMockResult);
-
const packageA = await fs.readJson(
mockDir.resolve('packages/a/package.json'),
);
@@ -312,11 +289,7 @@ describe('bump', () => {
'Using default pattern glob @backstage/*',
'Checking for updates of @backstage/core',
'Checking for updates of @backstage/theme',
- 'Checking for updates of @backstage/core-api',
'Some packages are outdated, updating',
- 'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
- 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
- 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/core in a to ^1.0.6',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/theme in b to ^2.0.0',
@@ -328,9 +301,8 @@ describe('bump', () => {
'Version bump complete!',
]);
- expect(mockFetchPackageInfo).toHaveBeenCalledTimes(3);
+ expect(mockFetchPackageInfo).toHaveBeenCalledTimes(2);
expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core');
- expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core-api');
expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme');
expect(runObj.run).not.toHaveBeenCalledWith(
@@ -339,12 +311,6 @@ describe('bump', () => {
expect.any(Object),
);
- const lockfileContents = await fs.readFile(
- mockDir.resolve('yarn.lock'),
- 'utf8',
- );
- expect(lockfileContents).toBe(lockfileMockResult);
-
const packageA = await fs.readJson(
mockDir.resolve('packages/a/package.json'),
);
@@ -425,12 +391,7 @@ describe('bump', () => {
'Using default pattern glob @backstage/*',
'Checking for updates of @backstage/core',
'Checking for updates of @backstage/theme',
- 'Checking for updates of @backstage/theme',
- 'Checking for updates of @backstage/core-api',
'Some packages are outdated, updating',
- 'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
- 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
- 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/theme in b to ^5.0.0',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/core in a to ^1.0.6',
@@ -443,9 +404,8 @@ describe('bump', () => {
'Version bump complete!',
]);
- expect(mockFetchPackageInfo).toHaveBeenCalledTimes(2);
+ expect(mockFetchPackageInfo).toHaveBeenCalledTimes(1);
expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core');
- expect(mockFetchPackageInfo).not.toHaveBeenCalledWith('@backstage/theme');
expect(runObj.run).toHaveBeenCalledTimes(1);
expect(runObj.run).toHaveBeenCalledWith(
@@ -454,12 +414,6 @@ describe('bump', () => {
expect.any(Object),
);
- const lockfileContents = await fs.readFile(
- mockDir.resolve('yarn.lock'),
- 'utf8',
- );
- expect(lockfileContents).toBe(lockfileMockResult);
-
const packageA = await fs.readJson(
mockDir.resolve('packages/a/package.json'),
);
@@ -629,12 +583,7 @@ describe('bump', () => {
'Using default pattern glob @backstage/*',
'Checking for updates of @backstage/core',
'Checking for updates of @backstage/theme',
- 'Checking for updates of @backstage/theme',
- 'Checking for updates of @backstage/core-api',
'Some packages are outdated, updating',
- 'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
- 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
- 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/theme in b to ^5.0.0',
'bumping @backstage/core in b to ^1.0.6',
'bumping @backstage/core in a to ^1.0.6',
@@ -658,21 +607,6 @@ describe('bump', () => {
"@backstage-extra/custom-two@^1.0.0":
version "1.0.0"
-`;
- const customLockfileMockResult = `${HEADER}
-"@backstage-extra/custom-two@^1.0.0":
- version "1.0.0"
-
-"@backstage-extra/custom@^1.1.0":
- version "1.1.0"
-
-"@backstage/core@^1.0.5":
- version "1.0.6"
- dependencies:
- "@backstage/core-api" "^1.0.6"
-
-"@backstage/theme@^1.0.0":
- version "1.0.0"
`;
mockDir.setContent({
'yarn.lock': customLockfileMock,
@@ -731,12 +665,7 @@ describe('bump', () => {
'Checking for updates of @backstage-extra/custom',
'Checking for updates of @backstage-extra/custom-two',
'Checking for updates of @backstage/theme',
- 'Checking for updates of @backstage/core-api',
'Some packages are outdated, updating',
- 'unlocking @backstage/core@^1.0.3 ~> 1.0.6',
- 'unlocking @backstage-extra/custom@^1.0.1 ~> 1.1.0',
- 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7',
- 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7',
'bumping @backstage/core in a to ^1.0.6',
'bumping @backstage-extra/custom in a to ^1.1.0',
'bumping @backstage-extra/custom-two in a to ^2.0.0',
@@ -754,7 +683,7 @@ describe('bump', () => {
'Version bump complete!',
]);
- expect(mockFetchPackageInfo).toHaveBeenCalledTimes(5);
+ expect(mockFetchPackageInfo).toHaveBeenCalledTimes(4);
expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core');
expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme');
@@ -765,12 +694,6 @@ describe('bump', () => {
expect.any(Object),
);
- const lockfileContents = await fs.readFile(
- mockDir.resolve('yarn.lock'),
- 'utf8',
- );
- expect(lockfileContents).toEqual(customLockfileMockResult);
-
const packageA = await fs.readJson(
mockDir.resolve('packages/a/package.json'),
);
@@ -798,7 +721,7 @@ describe('bump', () => {
it('should ignore not found packages', async () => {
mockDir.setContent({
- 'yarn.lock': lockfileMockResult,
+ 'yarn.lock': lockfileMock,
'package.json': JSON.stringify({
workspaces: {
packages: ['packages/*'],
@@ -848,21 +771,11 @@ describe('bump', () => {
'Checking for updates of @backstage/theme',
'Package info not found, ignoring package @backstage/core',
'Package info not found, ignoring package @backstage/theme',
- 'Checking for updates of @backstage/core',
- 'Checking for updates of @backstage/theme',
- 'Package info not found, ignoring package @backstage/core',
- 'Package info not found, ignoring package @backstage/theme',
'All Backstage packages are up to date!',
]);
expect(runObj.run).toHaveBeenCalledTimes(0);
- const lockfileContents = await fs.readFile(
- mockDir.resolve('yarn.lock'),
- 'utf8',
- );
- expect(lockfileContents).toBe(lockfileMockResult);
-
const packageA = await fs.readJson(
mockDir.resolve('packages/a/package.json'),
);
@@ -883,83 +796,6 @@ describe('bump', () => {
},
});
});
-
- // eslint-disable-next-line jest/expect-expect
- it('should log duplicates', async () => {
- jest.spyOn(Lockfile.prototype, 'analyze').mockReturnValue({
- invalidRanges: [],
- newVersions: [],
- newRanges: [
- {
- name: '@backstage/backend-app-api',
- oldRange: '^1.0.0',
- newRange: '^2.0.0',
- oldVersion: '1.0.0',
- newVersion: '2.0.0',
- },
- ],
- });
- mockDir.setContent({
- 'yarn.lock': `${HEADER}
-"@backstage/backend-app-api@^1.0.0":
- version "1.0.0"
-`,
- 'package.json': JSON.stringify({
- workspaces: {
- packages: ['packages/*'],
- },
- }),
- packages: {
- a: {
- 'package.json': JSON.stringify({
- name: 'a',
- dependencies: {
- '@backstage/backend-app-api': '^1.0.0',
- },
- }),
- },
- },
- });
-
- jest.spyOn(runObj, 'run').mockResolvedValue(undefined);
- worker.use(
- rest.get(
- 'https://versions.backstage.io/v1/tags/main/manifest.json',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.json({
- packages: [
- {
- name: '@backstage/backend-app-api',
- version: '2.0.0',
- },
- ],
- }),
- ),
- ),
- );
- const { log: logs } = await withLogCollector(['log', 'warn'], async () => {
- await bump({ pattern: null, release: 'main' } as unknown as Command);
- });
- expectLogsToMatch(logs, [
- 'Using default pattern glob @backstage/*',
- 'Checking for updates of @backstage/backend-app-api',
- 'Checking for updates of @backstage/backend-app-api',
- 'Some packages are outdated, updating',
- 'bumping @backstage/backend-app-api in a to ^2.0.0',
- 'Running yarn install to install new versions',
- 'Checking for moved packages to the @backstage-community namespace...',
- 'â ī¸ The following packages may have breaking changes:',
- ' @backstage/backend-app-api : 1.0.0 ~> 2.0.0',
- ' https://github.com/backstage/backstage/blob/master/packages/backend-app-api/CHANGELOG.md',
- 'Version bump complete!',
- ' â ī¸ Warning! â ī¸',
- ' The below package(s) have incompatible duplicate installations, likely due to a bad dependency in a plugin.',
- ' You can investigate this by running `yarn why `, and report the issue to the plugin maintainers.',
- ' @backstage/backend-app-api',
- ]);
- });
});
describe('bumpBackstageJsonVersion', () => {
diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts
index d6d5f424ba..5b981d3890 100644
--- a/packages/cli/src/commands/versions/bump.ts
+++ b/packages/cli/src/commands/versions/bump.ts
@@ -24,7 +24,6 @@ import fs from 'fs-extra';
import chalk from 'chalk';
import ora from 'ora';
import semver from 'semver';
-import { minimatch } from 'minimatch';
import { OptionValues } from 'commander';
import { isError, NotFoundError } from '@backstage/errors';
import { resolve as resolvePath } from 'path';
@@ -36,7 +35,6 @@ import {
Lockfile,
YarnInfoInspectData,
} from '../../lib/versioning';
-import { forbiddenDuplicatesFilter } from './lint';
import { BACKSTAGE_JSON } from '@backstage/cli-common';
import { runParallelWorkers } from '../../lib/parallel';
import {
@@ -44,7 +42,6 @@ import {
getManifestByVersion,
ReleaseManifest,
} from '@backstage/release-manifests';
-import { PackageGraph } from '@backstage/cli-node';
import { migrateMovedPackages } from './migrate';
function shouldUseGlobalAgent(): boolean {
@@ -125,8 +122,6 @@ export default async (opts: OptionValues) => {
// Next check with the package registry to see which dependency ranges we need to bump
const versionBumps = new Map();
- // Track package versions that we want to remove from yarn.lock in order to trigger a bump
- const unlocked = Array<{ name: string; range: string; target: string }>();
await runParallelWorkers({
parallelismFactor: 4,
@@ -157,72 +152,13 @@ export default async (opts: OptionValues) => {
},
});
- const filter = (name: string) => minimatch(name, pattern);
-
- // Check for updates of transitive backstage dependencies
- await runParallelWorkers({
- parallelismFactor: 4,
- items: lockfile.keys(),
- async worker(name) {
- // Only check @backstage packages and friends, we don't want this to do a full update of all deps
- if (!filter(name)) {
- return;
- }
-
- let target: string;
- try {
- target = await findTargetVersion(name);
- } catch (error) {
- if (isError(error) && error.name === 'NotFoundError') {
- console.log(`Package info not found, ignoring package ${name}`);
- return;
- }
- throw error;
- }
-
- for (const entry of lockfile.get(name) ?? []) {
- // Ignore lockfile entries that don't satisfy the version range, since
- // these can't cause the package to be locked to an older version
- if (!semver.satisfies(target, entry.range)) {
- continue;
- }
- // Unlock all entries that are within range but on the old version
- unlocked.push({ name, range: entry.range, target });
- }
- },
- });
-
- console.log();
-
// Write all discovered version bumps to package.json in this repo
- if (versionBumps.size === 0 && unlocked.length === 0) {
+ if (versionBumps.size === 0) {
console.log(chalk.green('All Backstage packages are up to date!'));
} else {
console.log(chalk.yellow('Some packages are outdated, updating'));
console.log();
- if (unlocked.length > 0) {
- const removed = new Set();
- for (const { name, range, target } of unlocked) {
- // Don't bother removing lockfile entries if they're already on the correct version
- const existingEntry = lockfile.get(name)?.find(e => e.range === range);
- if (existingEntry?.version === target) {
- continue;
- }
- const key = JSON.stringify({ name, range });
- if (!removed.has(key)) {
- removed.add(key);
- console.log(
- `${chalk.magenta('unlocking')} ${name}@${chalk.yellow(
- range,
- )} ~> ${chalk.yellow(target)}`,
- );
- lockfile.remove(name, range);
- }
- }
- await lockfile.save(lockfilePath);
- }
-
const breakingUpdates = new Map();
await runParallelWorkers({
parallelismFactor: 4,
@@ -334,38 +270,6 @@ export default async (opts: OptionValues) => {
}
console.log();
-
- // Finally we make sure the new lockfile doesn't have any duplicates
- const dedupLockfile = await Lockfile.load(lockfilePath);
-
- const result = dedupLockfile.analyze({
- filter,
- localPackages: PackageGraph.fromPackages(
- await PackageGraph.listTargetPackages(),
- ),
- });
-
- const forbiddenNewRanges = result.newRanges.filter(({ name }) =>
- forbiddenDuplicatesFilter(name),
- );
- if (forbiddenNewRanges.length > 0) {
- console.log(chalk.yellow(' â ī¸ Warning! â ī¸'));
- console.log();
- console.log(
- chalk.yellow(
- ' The below package(s) have incompatible duplicate installations, likely due to a bad dependency in a plugin.',
- ),
- );
- console.log(
- chalk.yellow(
- ' You can investigate this by running `yarn why `, and report the issue to the plugin maintainers.',
- ),
- );
- console.log();
- for (const { name } of forbiddenNewRanges) {
- console.log(chalk.yellow(` ${name}`));
- }
- }
};
export function createStrictVersionFinder(options: {
diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts
index bfb2a752a0..66b5fc04c4 100644
--- a/packages/cli/src/commands/versions/lint.ts
+++ b/packages/cli/src/commands/versions/lint.ts
@@ -14,116 +14,8 @@
* limitations under the License.
*/
-import { OptionValues } from 'commander';
-import { Lockfile } from '../../lib/versioning';
-import { paths } from '../../lib/paths';
-import partition from 'lodash/partition';
-import { PackageGraph } from '@backstage/cli-node';
-
-// Packages that we try to avoid duplicates for
-const INCLUDED = [/^@backstage\//];
-
-export const includedFilter = (name: string) =>
- INCLUDED.some(pattern => pattern.test(name));
-
-// Packages that are not allowed to have any duplicates
-const FORBID_DUPLICATES = [/^@backstage\/\w+-app-api$/, /^@backstage\/plugin-/];
-
-// There are some packages that ARE explicitly allowed to have duplicates since
-// they handle that appropriately. This takes precedence over FORBID_DUPLICATES
-// above.
-const ALLOW_DUPLICATES = [
- /^@backstage\/core-plugin-api$/,
- // Duplicates of libraries are OK
- // TODO(Rugvip): Check this using package role instead
- /^@backstage\/plugin-.*-react$/,
- /^@backstage\/plugin-.*-node$/,
- /^@backstage\/plugin-.*-common$/,
-];
-
-export const forbiddenDuplicatesFilter = (name: string) =>
- FORBID_DUPLICATES.some(pattern => pattern.test(name)) &&
- !ALLOW_DUPLICATES.some(pattern => pattern.test(name));
-
-export default async (cmd: OptionValues) => {
- const fix = Boolean(cmd.fix);
-
- let success = true;
-
- const lockfilePath = paths.resolveTargetRoot('yarn.lock');
- const lockfile = await Lockfile.load(lockfilePath);
- const result = lockfile.analyze({
- filter: includedFilter,
- localPackages: PackageGraph.fromPackages(
- await PackageGraph.listTargetPackages(),
- ),
- });
-
- logArray(
- result.invalidRanges,
- "The following packages versions are invalid and can't be analyzed:",
- e => ` ${e.name} @ ${e.range}`,
+export default async () => {
+ throw new Error(
+ 'This command has been removed, please consider alternatives such as `yarn dedupe` instead.',
);
-
- if (fix) {
- lockfile.replaceVersions(result.newVersions);
- await lockfile.save(lockfilePath);
- } else {
- const [newVersionsForbidden, newVersionsAllowed] = partition(
- result.newVersions,
- ({ name }) => forbiddenDuplicatesFilter(name),
- );
- if (newVersionsForbidden.length && !fix) {
- success = false;
- }
-
- logArray(
- newVersionsForbidden,
- 'The following packages must be deduplicated, this can be done automatically with --fix',
- e =>
- ` ${e.name} @ ${e.range} bumped from ${e.oldVersion} to ${e.newVersion}`,
- );
- logArray(
- newVersionsAllowed,
- 'The following packages can be deduplicated, this can be done automatically with --fix',
- e =>
- ` ${e.name} @ ${e.range} bumped from ${e.oldVersion} to ${e.newVersion}`,
- );
- }
-
- const [newRangesForbidden, newRangesAllowed] = partition(
- result.newRanges,
- ({ name }) => forbiddenDuplicatesFilter(name),
- );
- if (newRangesForbidden.length) {
- success = false;
- }
-
- logArray(
- newRangesForbidden,
- 'The following packages must be deduplicated by updating dependencies in package.json',
- e => ` ${e.name} @ ${e.oldRange} should be changed to ${e.newRange}`,
- );
- logArray(
- newRangesAllowed,
- 'The following packages can be deduplicated by updating dependencies in package.json',
- e => ` ${e.name} @ ${e.oldRange} should be changed to ${e.newRange}`,
- );
-
- if (!success) {
- throw new Error('Failed versioning check');
- }
};
-
-function logArray(arr: T[], header: string, each: (item: T) => string) {
- if (arr.length === 0) {
- return;
- }
-
- console.log(header);
- console.log();
- for (const e of arr) {
- console.log(each(e));
- }
- console.log();
-}
diff --git a/packages/cli/src/lib/bundler/bundle.ts b/packages/cli/src/lib/bundler/bundle.ts
index d24d4252f4..0aa91a90a6 100644
--- a/packages/cli/src/lib/bundler/bundle.ts
+++ b/packages/cli/src/lib/bundler/bundle.ts
@@ -42,6 +42,7 @@ export async function buildBundle(options: BuildOptions) {
const paths = resolveBundlingPaths(options);
const publicPaths = await resolveOptionalBundlingPaths({
+ targetDir: options.targetDir,
entry: 'src/index-public-experimental',
dist: 'dist/public',
});
diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts
index 60c8a7be66..42cdae24e3 100644
--- a/packages/cli/src/lib/bundler/config.ts
+++ b/packages/cli/src/lib/bundler/config.ts
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-import { BackendBundlingOptions, BundlingOptions } from './types';
-import { posix as posixPath, resolve as resolvePath } from 'path';
+import {
+ BackendBundlingOptions,
+ BundlingOptions,
+ ModuleFederationOptions,
+} from './types';
+import { posix as posixPath, resolve as resolvePath, dirname } from 'path';
+import chalk from 'chalk';
import webpack, { ProvidePlugin } from 'webpack';
import { BackstagePackage } from '@backstage/cli-node';
@@ -28,12 +33,13 @@ import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack';
import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin';
import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin';
import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin';
+import ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import { paths as cliPaths } from '../../lib/paths';
import fs from 'fs-extra';
import { getPackages } from '@manypkg/get-packages';
import { isChildPath } from '@backstage/cli-common';
import nodeExternals from 'webpack-node-externals';
-import { optimization } from './optimization';
+import { optimization as optimizationConfig } from './optimization';
import pickBy from 'lodash/pickBy';
import { readEntryPoints } from '../entryPoints';
import { runPlain } from '../run';
@@ -44,15 +50,42 @@ import { hasReactDomClient } from './hasReactDomClient';
const BUILD_CACHE_ENV_VAR = 'BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE';
-export function resolveBaseUrl(config: Config): URL {
+export function resolveBaseUrl(
+ config: Config,
+ moduleFederation?: ModuleFederationOptions,
+): URL {
const baseUrl = config.getOptionalString('app.baseUrl');
+
+ const defaultBaseUrl =
+ moduleFederation?.mode === 'remote'
+ ? `http://localhost:${process.env.PORT ?? '3000'}`
+ : 'http://localhost:3000';
+
try {
- return new URL(baseUrl ?? '/', 'http://localhost:3000');
+ return new URL(baseUrl ?? '/', defaultBaseUrl);
} catch (error) {
throw new Error(`Invalid app.baseUrl, ${error}`);
}
}
+export function resolveEndpoint(
+ config: Config,
+ moduleFederation?: ModuleFederationOptions,
+): {
+ host: string;
+ port: number;
+} {
+ const url = resolveBaseUrl(config, moduleFederation);
+
+ return {
+ host: config.getOptionalString('app.listen.host') ?? url.hostname,
+ port:
+ config.getOptionalNumber('app.listen.port') ??
+ Number(url.port) ??
+ (url.protocol === 'https:' ? 443 : 80),
+ };
+}
+
async function readBuildInfo() {
const timestamp = Date.now();
@@ -93,7 +126,13 @@ export async function createConfig(
paths: BundlingPaths,
options: BundlingOptions,
): Promise {
- const { checksEnabled, isDev, frontendConfig, publicSubPath = '' } = options;
+ const {
+ checksEnabled,
+ isDev,
+ frontendConfig,
+ moduleFederation,
+ publicSubPath = '',
+ } = options;
const { plugins, loaders } = transforms(options);
// Any package that is part of the monorepo but outside the monorepo root dir need
@@ -101,12 +140,29 @@ export async function createConfig(
const { packages } = await getPackages(cliPaths.targetDir);
const externalPkgs = packages.filter(p => !isChildPath(paths.root, p.dir));
- const validBaseUrl = resolveBaseUrl(frontendConfig);
+ const validBaseUrl = resolveBaseUrl(frontendConfig, moduleFederation);
let publicPath = validBaseUrl.pathname.replace(/\/$/, '');
if (publicSubPath) {
publicPath = `${publicPath}${publicSubPath}`.replace('//', '/');
}
+ if (isDev) {
+ const { host, port } = resolveEndpoint(
+ options.frontendConfig,
+ options.moduleFederation,
+ );
+
+ plugins.push(
+ new ReactRefreshPlugin({
+ overlay: {
+ sockProtocol: 'ws',
+ sockHost: host,
+ sockPort: port,
+ },
+ }),
+ );
+ }
+
if (checksEnabled) {
plugins.push(
new ForkTsCheckerWebpackPlugin({
@@ -232,12 +288,53 @@ export async function createConfig(
require.resolve('react-refresh'),
];
+ const mode = isDev ? 'development' : 'production';
+ const optimization = optimizationConfig(options);
+
+ if (
+ mode === 'production' &&
+ process.env.EXPERIMENTAL_MODULE_FEDERATION &&
+ process.env.FORCE_REACT_DEVELOPMENT
+ ) {
+ console.log(
+ chalk.yellow(
+ `â ī¸ WARNING: Forcing react and react-dom into development mode. This build should not be used in production.`,
+ ),
+ );
+
+ const reactPackageDirs = [
+ `${dirname(require.resolve('react/package.json'))}/`,
+ `${dirname(require.resolve('react-dom/package.json'))}/`,
+ ];
+
+ // Don't define process.env.NODE_ENV with value matching config.mode. If we
+ // don't set this to false, webpack will define the value of
+ // process.env.NODE_ENV for us, and the definition below will be ignored.
+ optimization.nodeEnv = false;
+
+ // Instead, provide a custom definition which always uses "development" if
+ // the module is part of `react` or `react-dom`, and `config.mode` otherwise.
+ plugins.push(
+ new webpack.DefinePlugin({
+ 'process.env.NODE_ENV': webpack.DefinePlugin.runtimeValue(
+ ({ module }) => {
+ if (reactPackageDirs.some(val => module.resource.startsWith(val))) {
+ return '"development"';
+ }
+
+ return `"${mode}"`;
+ },
+ ),
+ }),
+ );
+ }
+
const withCache = yn(process.env[BUILD_CACHE_ENV_VAR], { default: false });
return {
- mode: isDev ? 'development' : 'production',
+ mode,
profile: false,
- optimization: optimization(options),
+ optimization,
bail: false,
performance: {
hints: false, // we check the gzip size instead
diff --git a/packages/cli/src/lib/bundler/index.ts b/packages/cli/src/lib/bundler/index.ts
index a3b584efc4..2219784dc9 100644
--- a/packages/cli/src/lib/bundler/index.ts
+++ b/packages/cli/src/lib/bundler/index.ts
@@ -16,4 +16,5 @@
export { serveBackend } from './backend';
export { buildBundle } from './bundle';
+export { getModuleFederationOptions } from './moduleFederation';
export { serveBundle } from './server';
diff --git a/packages/cli/src/lib/bundler/moduleFederation.ts b/packages/cli/src/lib/bundler/moduleFederation.ts
new file mode 100644
index 0000000000..ae3f9ab048
--- /dev/null
+++ b/packages/cli/src/lib/bundler/moduleFederation.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 chalk from 'chalk';
+import { ModuleFederationOptions } from './types';
+
+export function getModuleFederationOptions(
+ name: string,
+ isModuleFederationRemote?: boolean,
+): ModuleFederationOptions | undefined {
+ if (
+ !isModuleFederationRemote &&
+ !process.env.EXPERIMENTAL_MODULE_FEDERATION
+ ) {
+ return undefined;
+ }
+
+ console.log(
+ chalk.yellow(
+ `â ī¸ WARNING: Module federation is experimental and will receive immediate breaking changes in the future.`,
+ ),
+ );
+
+ return {
+ mode: isModuleFederationRemote ? 'remote' : 'host',
+ // The default output mode requires the name to be a usable as a code
+ // symbol, there might be better options here but for now we need to
+ // sanitize the name.
+ name: name.replaceAll('@', '').replaceAll('/', '__').replaceAll('-', '_'),
+ };
+}
diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts
index 891e0793fe..cf240b4791 100644
--- a/packages/cli/src/lib/bundler/optimization.ts
+++ b/packages/cli/src/lib/bundler/optimization.ts
@@ -28,13 +28,13 @@ export const optimization = (
minimize: !isDev,
minimizer: [
new EsbuildPlugin({
- target: 'es2019',
+ target: 'ES2022',
format: 'iife',
exclude: 'remoteEntry.js',
}),
// Avoid iife wrapping of module federation remote entry as it breaks the variable assignment
new EsbuildPlugin({
- target: 'es2019',
+ target: 'ES2022',
format: undefined,
include: 'remoteEntry.js',
}),
diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts
index 1da15cd37e..259f3263eb 100644
--- a/packages/cli/src/lib/bundler/server.ts
+++ b/packages/cli/src/lib/bundler/server.ts
@@ -14,23 +14,16 @@
* limitations under the License.
*/
-import { PackageGraph } from '@backstage/cli-node';
import { AppConfig } from '@backstage/config';
import chalk from 'chalk';
import fs from 'fs-extra';
-import uniq from 'lodash/uniq';
import openBrowser from 'react-dev-utils/openBrowser';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
-import {
- forbiddenDuplicatesFilter,
- includedFilter,
-} from '../../commands/versions/lint';
import { paths as libPaths } from '../../lib/paths';
import { loadCliConfig } from '../config';
-import { Lockfile } from '../versioning';
-import { createConfig, resolveBaseUrl } from './config';
+import { createConfig, resolveBaseUrl, resolveEndpoint } from './config';
import { createDetectedModulesEntryPoint } from './packageDetection';
import { resolveBundlingPaths, resolveOptionalBundlingPaths } from './paths';
import { ServeOptions } from './types';
@@ -41,38 +34,6 @@ export async function serveBundle(options: ServeOptions) {
const targetPkg = await fs.readJson(paths.targetPackageJson);
if (options.verifyVersions) {
- const lockfile = await Lockfile.load(
- libPaths.resolveTargetRoot('yarn.lock'),
- );
- const result = lockfile.analyze({
- filter: includedFilter,
- localPackages: PackageGraph.fromPackages(
- await PackageGraph.listTargetPackages(),
- ),
- });
- const problemPackages = [...result.newVersions, ...result.newRanges]
- .map(({ name }) => name)
- .filter(forbiddenDuplicatesFilter);
-
- if (problemPackages.length > 1) {
- console.log(
- chalk.yellow(
- `â ī¸ Some of the following packages may be outdated or have duplicate installations:
-
- ${uniq(problemPackages).join(', ')}
- `,
- ),
- );
- console.log(
- chalk.yellow(
- `â ī¸ This can be resolved using the following command:
-
- yarn backstage-cli versions:check --fix
- `,
- ),
- );
- }
-
if (
targetPkg.dependencies?.['react-router']?.includes('beta') ||
targetPkg.dependencies?.['react-router-dom']?.includes('beta')
@@ -130,14 +91,11 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
}
const { frontendConfig, fullConfig } = cliConfig;
- const url = resolveBaseUrl(frontendConfig);
-
- const host =
- frontendConfig.getOptionalString('app.listen.host') || url.hostname;
- const port =
- frontendConfig.getOptionalNumber('app.listen.port') ||
- Number(url.port) ||
- (url.protocol === 'https:' ? 443 : 80);
+ const url = resolveBaseUrl(frontendConfig, options.moduleFederation);
+ const { host, port } = resolveEndpoint(
+ frontendConfig,
+ options.moduleFederation,
+ );
const detectedModulesEntryPoint = await createDetectedModulesEntryPoint({
config: fullConfig,
@@ -162,6 +120,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
const config = await createConfig(paths, {
...commonConfigOptions,
additionalEntryPoints: detectedModulesEntryPoint,
+ moduleFederation: options.moduleFederation,
});
if (process.env.EXPERIMENTAL_VITE) {
@@ -232,14 +191,17 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
directory: paths.targetPublic,
}
: undefined,
- historyApiFallback: {
- // Paths with dots should still use the history fallback.
- // See https://github.com/facebookincubator/create-react-app/issues/387.
- disableDotRule: true,
+ historyApiFallback:
+ options.moduleFederation?.mode === 'remote'
+ ? false
+ : {
+ // Paths with dots should still use the history fallback.
+ // See https://github.com/facebookincubator/create-react-app/issues/387.
+ disableDotRule: true,
- // The index needs to be rewritten relative to the new public path, including subroutes.
- index: `${config.output?.publicPath}index.html`,
- },
+ // The index needs to be rewritten relative to the new public path, including subroutes.
+ index: `${config.output?.publicPath}index.html`,
+ },
server:
url.protocol === 'https:'
? {
@@ -256,7 +218,13 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
// When the dev server is behind a proxy, the host and public hostname differ
allowedHosts: [url.hostname],
client: {
- webSocketURL: 'auto://0.0.0.0:0/ws',
+ webSocketURL: { hostname: host, port },
+ },
+ headers: {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
+ 'Access-Control-Allow-Headers':
+ 'X-Requested-With, content-type, Authorization',
},
},
compiler,
@@ -278,7 +246,9 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
}
});
- openBrowser(url.href);
+ if (!options.skipOpenBrowser) {
+ openBrowser(url.href);
+ }
const waitForExit = async () => {
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts
index da1eec4e11..302b2fbe68 100644
--- a/packages/cli/src/lib/bundler/transforms.ts
+++ b/packages/cli/src/lib/bundler/transforms.ts
@@ -17,7 +17,6 @@
import { ModuleOptions, WebpackPluginInstance } from 'webpack';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import { svgrTemplate } from '../svgrTemplate';
-import ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin';
type Transforms = {
loaders: ModuleOptions['rules'];
@@ -58,7 +57,7 @@ export const transforms = (options: TransformOptions): Transforms => {
loader: require.resolve('swc-loader'),
options: {
jsc: {
- target: 'es2019',
+ target: 'es2022',
externalHelpers: !isBackend,
parser: {
syntax: 'typescript',
@@ -86,7 +85,7 @@ export const transforms = (options: TransformOptions): Transforms => {
loader: require.resolve('swc-loader'),
options: {
jsc: {
- target: 'es2019',
+ target: 'es2022',
externalHelpers: !isBackend,
parser: {
syntax: 'ecmascript',
@@ -119,7 +118,7 @@ export const transforms = (options: TransformOptions): Transforms => {
loader: require.resolve('swc-loader'),
options: {
jsc: {
- target: 'es2019',
+ target: 'es2022',
externalHelpers: !isBackend,
parser: {
syntax: 'ecmascript',
@@ -193,15 +192,7 @@ export const transforms = (options: TransformOptions): Transforms => {
const plugins = new Array();
- if (isDev) {
- if (!isBackend) {
- plugins.push(
- new ReactRefreshPlugin({
- overlay: { sockProtocol: 'ws' },
- }),
- );
- }
- } else {
+ if (!isDev) {
plugins.push(
new MiniCssExtractPlugin({
filename: 'static/[name].[contenthash:8].css',
diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts
index 7291ac0818..f8e228c5c4 100644
--- a/packages/cli/src/lib/bundler/types.ts
+++ b/packages/cli/src/lib/bundler/types.ts
@@ -43,6 +43,8 @@ export type ServeOptions = BundlingPathsOptions & {
checksEnabled: boolean;
configPaths: string[];
verifyVersions?: boolean;
+ skipOpenBrowser?: boolean;
+ moduleFederation?: ModuleFederationOptions;
};
export type BuildOptions = BundlingPathsOptions & {
diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts
index 0c657b640e..49cce1f1ed 100644
--- a/packages/cli/src/lib/config.ts
+++ b/packages/cli/src/lib/config.ts
@@ -14,14 +14,9 @@
* limitations under the License.
*/
-import {
- ConfigTarget,
- loadConfig,
- loadConfigSchema,
-} from '@backstage/config-loader';
+import { ConfigSources, loadConfigSchema } from '@backstage/config-loader';
import { AppConfig, ConfigReader } from '@backstage/config';
import { paths } from './paths';
-import { isValidUrl } from './urls';
import { getPackages } from '@manypkg/get-packages';
import { PackageGraph } from '@backstage/cli-node';
@@ -37,13 +32,6 @@ type Options = {
};
export async function loadCliConfig(options: Options) {
- const configTargets: ConfigTarget[] = [];
- options.args.forEach(arg => {
- if (!isValidUrl(arg)) {
- configTargets.push({ path: paths.resolveTarget(arg) });
- }
- });
-
// Consider all packages in the monorepo when loading in config
const { packages } = await getPackages(paths.targetDir);
@@ -75,32 +63,62 @@ export async function loadCliConfig(options: Options) {
noUndeclaredProperties: options.strict,
});
- const { appConfigs } = await loadConfig({
- experimentalEnvFunc: options.mockEnv
+ const source = ConfigSources.default({
+ allowMissingDefaultConfig: true,
+ substitutionFunc: options.mockEnv
? async name => process.env[name] || 'x'
: undefined,
- configRoot: paths.targetRoot,
- configTargets: configTargets,
- watch: options.watch && {
- onChange(newAppConfigs) {
- const newFrontendAppConfigs = schema.process(newAppConfigs, {
- visibility: options.fullVisibility
- ? ['frontend', 'backend', 'secret']
- : ['frontend'],
- withFilteredKeys: options.withFilteredKeys,
- withDeprecatedKeys: options.withDeprecatedKeys,
- ignoreSchemaErrors: !options.strict,
- });
- options.watch?.(newFrontendAppConfigs);
- },
- },
+ watch: Boolean(options.watch),
+ rootDir: paths.targetRoot,
+ argv: options.args,
});
+ const appConfigs = await new Promise((resolve, reject) => {
+ async function loadConfigReaderLoop() {
+ let loaded = false;
+
+ try {
+ const abortController = new AbortController();
+ for await (const { configs } of source.readConfigData({
+ signal: abortController.signal,
+ })) {
+ if (loaded) {
+ const newFrontendAppConfigs = schema.process(configs, {
+ visibility: options.fullVisibility
+ ? ['frontend', 'backend', 'secret']
+ : ['frontend'],
+ withFilteredKeys: options.withFilteredKeys,
+ withDeprecatedKeys: options.withDeprecatedKeys,
+ ignoreSchemaErrors: !options.strict,
+ });
+ options.watch?.(newFrontendAppConfigs);
+ } else {
+ resolve(configs);
+ loaded = true;
+
+ if (!options.watch) {
+ abortController.abort();
+ }
+ }
+ }
+ } catch (error) {
+ if (loaded) {
+ console.error(`Failed to reload configuration, ${error}`);
+ } else {
+ reject(error);
+ }
+ }
+ }
+ loadConfigReaderLoop();
+ });
+
+ const configurationLoadedMessage = appConfigs.length
+ ? `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`
+ : `No configuration files found, running without config`;
+
// printing to stderr to not clobber stdout in case the cli command
// outputs structured data (e.g. as config:schema does)
- process.stderr.write(
- `Loaded config from ${appConfigs.map(c => c.context).join(', ')}\n`,
- );
+ process.stderr.write(`${configurationLoadedMessage}\n`);
try {
const frontendAppConfigs = schema.process(appConfigs, {
diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts
index a4267acdb7..d6996b9143 100644
--- a/packages/cli/src/lib/versioning/Lockfile.test.ts
+++ b/packages/cli/src/lib/versioning/Lockfile.test.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import fs from 'fs-extra';
-import { BackstagePackage } from '@backstage/cli-node';
import { Lockfile } from './Lockfile';
import { createMockDirectory } from '@backstage/backend-test-utils';
@@ -47,34 +45,6 @@ b@^2:
version "2.0.0"
`;
-const mockADedup = `${LEGACY_HEADER}
-a@^1:
- version "1.0.1"
- resolved "https://my-registry/a-1.0.01.tgz#abc123"
- integrity sha512-xyz
- dependencies:
- b "^2"
-
-b@2.0.x, b@^2:
- version "2.0.1"
-`;
-
-const mockB = `${LEGACY_HEADER}
-"@s/a@*", "@s/a@1 || 2", "@s/a@^1":
- version "1.0.1"
-
-"@s/a@^2.0.x":
- version "2.0.0"
-`;
-
-const mockBDedup = `${LEGACY_HEADER}
-"@s/a@*", "@s/a@1 || 2", "@s/a@^2.0.x":
- version "2.0.0"
-
-"@s/a@^1":
- version "1.0.1"
-`;
-
describe('Lockfile', () => {
const mockDir = createMockDirectory();
@@ -93,75 +63,6 @@ describe('Lockfile', () => {
]);
expect(lockfile.toString()).toBe(mockA);
});
-
- it('should deduplicate and save mockA', async () => {
- mockDir.setContent({
- 'yarn.lock': mockA,
- });
-
- const lockfilePath = mockDir.resolve('yarn.lock');
- const lockfile = await Lockfile.load(lockfilePath);
- const result = lockfile.analyze({ localPackages: new Map() });
- expect(result).toEqual({
- invalidRanges: [],
- newRanges: [],
- newVersions: [
- {
- name: 'b',
- range: '^2',
- oldVersion: '2.0.0',
- newVersion: '2.0.1',
- },
- ],
- });
-
- expect(lockfile.toString()).toBe(mockA);
- lockfile.replaceVersions(result.newVersions);
- expect(lockfile.toString()).toBe(mockADedup);
-
- await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(mockA);
- await expect(lockfile.save(lockfilePath)).resolves.toBeUndefined();
- await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(mockADedup);
- });
-
- it('should deduplicate mockB', async () => {
- mockDir.setContent({
- 'yarn.lock': mockB,
- });
-
- const lockfile = await Lockfile.load(mockDir.resolve('yarn.lock'));
- const result = lockfile.analyze({ localPackages: new Map() });
- expect(result).toEqual({
- invalidRanges: [],
- newRanges: [
- {
- name: '@s/a',
- oldRange: '^1',
- newRange: '^2.0.x',
- oldVersion: '1.0.1',
- newVersion: '2.0.0',
- },
- ],
- newVersions: [
- {
- name: '@s/a',
- range: '*',
- oldVersion: '1.0.1',
- newVersion: '2.0.0',
- },
- {
- name: '@s/a',
- range: '1 || 2',
- oldVersion: '1.0.1',
- newVersion: '2.0.0',
- },
- ],
- });
-
- expect(lockfile.toString()).toBe(mockB);
- lockfile.replaceVersions(result.newVersions);
- expect(lockfile.toString()).toBe(mockBDedup);
- });
});
const mockANew = `${MODERN_HEADER}
@@ -179,51 +80,6 @@ b@^2:
version: 2.0.0
`;
-const mockANewDedup = `${MODERN_HEADER}
-a@^1:
- version: 1.0.1
- dependencies:
- b: ^2
- integrity: sha512-xyz
- resolved: "https://my-registry/a-1.0.01.tgz#abc123"
-
-"b@2.0.x, b@^2.0.1":
- version: 2.0.1
-
-b@^2:
- version: 2.0.1
-`;
-
-const mockANewLocal = `${MODERN_HEADER}
-a@^1:
- version: 1.0.1
- dependencies:
- b: ^2
- integrity: sha512-xyz
- resolved: "https://my-registry/a-1.0.01.tgz#abc123"
-
-"b@2.0.x, b@^2.0.1":
- version: 0.0.0-use.local
-
-b@^2:
- version: 2.0.0
-`;
-
-const mockANewLocalDedup = `${MODERN_HEADER}
-a@^1:
- version: 1.0.1
- dependencies:
- b: ^2
- integrity: sha512-xyz
- resolved: "https://my-registry/a-1.0.01.tgz#abc123"
-
-"b@2.0.x, b@^2.0.1":
- version: 0.0.0-use.local
-
-b@^2:
- version: 0.0.0-use.local
-`;
-
describe('New Lockfile', () => {
const mockDir = createMockDirectory();
@@ -243,79 +99,4 @@ describe('New Lockfile', () => {
]);
expect(lockfile.toString()).toBe(mockANew);
});
-
- it('should deduplicate and save mockANew', async () => {
- mockDir.setContent({
- 'yarn.lock': mockANew,
- });
-
- const lockfilePath = mockDir.resolve('yarn.lock');
- const lockfile = await Lockfile.load(lockfilePath);
- const result = lockfile.analyze({ localPackages: new Map() });
- expect(result).toEqual({
- invalidRanges: [],
- newRanges: [],
- newVersions: [
- {
- name: 'b',
- range: '^2',
- oldVersion: '2.0.0',
- newVersion: '2.0.1',
- },
- ],
- });
-
- expect(lockfile.toString()).toBe(mockANew);
- lockfile.replaceVersions(result.newVersions);
- expect(lockfile.toString()).toBe(mockANewDedup);
-
- await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(mockANew);
- await expect(lockfile.save(lockfilePath)).resolves.toBeUndefined();
- await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(
- mockANewDedup,
- );
- });
-
- it('should deduplicate and save mockANewLocal', async () => {
- mockDir.setContent({
- 'yarn.lock': mockANewLocal,
- });
-
- const lockfilePath = mockDir.resolve('yarn.lock');
- const lockfile = await Lockfile.load(lockfilePath);
- const result = lockfile.analyze({
- localPackages: new Map([
- [
- 'b',
- {
- packageJson: { version: '2.0.1' },
- } as BackstagePackage,
- ],
- ]),
- });
- expect(result).toEqual({
- invalidRanges: [],
- newRanges: [],
- newVersions: [
- {
- name: 'b',
- range: '^2',
- oldVersion: '2.0.0',
- newVersion: '0.0.0-use.local',
- },
- ],
- });
-
- expect(lockfile.toString()).toBe(mockANewLocal);
- lockfile.replaceVersions(result.newVersions);
- expect(lockfile.toString()).toBe(mockANewLocalDedup);
-
- await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(
- mockANewLocal,
- );
- await expect(lockfile.save(lockfilePath)).resolves.toBeUndefined();
- await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(
- mockANewLocalDedup,
- );
- });
});
diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts
index 50d0a77cfd..047cf5aefc 100644
--- a/packages/cli/src/lib/versioning/Lockfile.ts
+++ b/packages/cli/src/lib/versioning/Lockfile.ts
@@ -15,10 +15,8 @@
*/
import fs from 'fs-extra';
-import semver from 'semver';
import { parseSyml, stringifySyml } from '@yarnpkg/parsers';
import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile';
-import { BackstagePackage } from '@backstage/cli-node';
const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
@@ -39,35 +37,6 @@ type LockfileQueryEntry = {
dataKey: string;
};
-/** Entries that have an invalid version range, for example an npm tag */
-type AnalyzeResultInvalidRange = {
- name: string;
- range: string;
-};
-
-/** Entries that can be deduplicated by bumping to an existing higher version */
-type AnalyzeResultNewVersion = {
- name: string;
- range: string;
- oldVersion: string;
- newVersion: string;
-};
-
-/** Entries that would need a dependency update in package.json to be deduplicated */
-type AnalyzeResultNewRange = {
- name: string;
- oldRange: string;
- newRange: string;
- oldVersion: string;
- newVersion: string;
-};
-
-type AnalyzeResult = {
- invalidRanges: AnalyzeResultInvalidRange[];
- newVersions: AnalyzeResultNewVersion[];
- newRanges: AnalyzeResultNewRange[];
-};
-
// the new yarn header is handled out of band of the parsing
// https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746
const NEW_HEADER = `${[
@@ -153,186 +122,6 @@ export class Lockfile {
return this.packages.keys();
}
- /** Analyzes the lockfile to identify possible actions and warnings for the entries */
- analyze(options: {
- filter?: (name: string) => boolean;
- localPackages: Map;
- }): AnalyzeResult {
- const { filter, localPackages } = options;
- const result: AnalyzeResult = {
- invalidRanges: [],
- newVersions: [],
- newRanges: [],
- };
-
- for (const [name, allEntries] of this.packages) {
- if (filter && !filter(name)) {
- continue;
- }
-
- // Get rid of and signal any invalid ranges upfront
- const invalid = allEntries.filter(
- e => !semver.validRange(e.range) && !e.range.startsWith('workspace:'),
- );
- result.invalidRanges.push(
- ...invalid.map(({ range }) => ({ name, range })),
- );
-
- // Grab all valid entries, if there aren't at least 2 different valid ones we're done
- const entries = allEntries.filter(e => semver.validRange(e.range));
- if (entries.length < 2) {
- continue;
- }
-
- // Find all versions currently in use
- const versions = Array.from(new Set(entries.map(e => e.version)))
- .map(v => {
- // Translate workspace:^ references to the actual version
- if (v === '0.0.0-use.local') {
- const local = localPackages.get(name);
- if (!local) {
- throw new Error(`No local package found for ${name}`);
- }
- if (!local.packageJson.version) {
- throw new Error(`No version found for local package ${name}`);
- }
- return {
- entryVersion: v,
- actualVersion: local.packageJson.version,
- };
- }
- return { entryVersion: v, actualVersion: v };
- })
- .sort((v1, v2) => semver.rcompare(v1.actualVersion, v2.actualVersion));
-
- // If we're not using at least 2 different versions we're done
- if (versions.length < 2) {
- continue;
- }
-
- // TODO(Rugvip): Support bumping into workspace ranges too
- const acceptedVersions = new Set();
- for (const { version, range } of entries) {
- // Finds the highest matching version from the the known versions
- // TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one
- const acceptedVersion = versions.find(v =>
- semver.satisfies(v.actualVersion, range),
- );
- if (!acceptedVersion) {
- throw new Error(
- `No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`,
- );
- }
-
- if (acceptedVersion.entryVersion !== version) {
- result.newVersions.push({
- name,
- range,
- newVersion: acceptedVersion.entryVersion,
- oldVersion: version,
- });
- }
-
- acceptedVersions.add(acceptedVersion.actualVersion);
- }
-
- // If all ranges were able to accept the same version, we're done
- if (acceptedVersions.size === 1) {
- continue;
- }
-
- // Find the max version that we may want bump older packages to
- const maxVersion = Array.from(acceptedVersions).sort(semver.rcompare)[0];
- // Find all existing ranges that satisfy the new max version, and pick the one that
- // results in the highest minimum allowed version, usually being the more specific one
- const maxEntry = entries
- .filter(e => semver.satisfies(maxVersion, e.range))
- .map(e => ({ e, min: semver.minVersion(e.range) }))
- .filter(p => p.min)
- .sort((a, b) => semver.rcompare(a.min!, b.min!))[0]?.e;
- if (!maxEntry) {
- throw new Error(
- `No entry found that satisfies max version '${maxVersion}'`,
- );
- }
-
- // Find all entries that don't satisfy the max version
- for (const { version, range } of entries) {
- if (semver.satisfies(maxVersion, range)) {
- continue;
- }
-
- result.newRanges.push({
- name,
- oldRange: range,
- newRange: maxEntry.range,
- oldVersion: version,
- newVersion: maxVersion,
- });
- }
- }
-
- return result;
- }
-
- remove(name: string, range: string): boolean {
- const query = `${name}@${range}`;
- const existed = Boolean(this.data[query]);
- delete this.data[query];
-
- const newEntries = this.packages.get(name)?.filter(e => e.range !== range);
- if (newEntries) {
- this.packages.set(name, newEntries);
- }
-
- return existed;
- }
-
- /** Modifies the lockfile by bumping packages to the suggested versions */
- replaceVersions(results: AnalyzeResultNewVersion[]) {
- for (const { name, range, oldVersion, newVersion } of results) {
- const query = `${name}@${range}`;
-
- // Update the backing data
- const entryData = this.data[query];
- if (!entryData) {
- throw new Error(`No entry data for ${query}`);
- }
- if (entryData.version !== oldVersion) {
- throw new Error(
- `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`,
- );
- }
-
- // Modifying the data in the entry is not enough, we need to reference an existing version object
- const matchingEntry = Object.entries(this.data).find(
- ([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion,
- );
- if (!matchingEntry) {
- throw new Error(
- `No matching entry found for ${name} at version ${newVersion}`,
- );
- }
- this.data[query] = matchingEntry[1];
-
- // Update our internal data structure
- const entry = this.packages.get(name)?.find(e => e.range === range);
- if (!entry) {
- throw new Error(`No entry data for ${query}`);
- }
- if (entry.version !== oldVersion) {
- throw new Error(
- `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`,
- );
- }
- entry.version = newVersion;
- }
- }
-
- async save(path: string) {
- await fs.writeFile(path, this.toString(), 'utf8');
- }
-
toString() {
return this.legacy
? legacyStringifyLockfile(this.data)
diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs
index 3b808fc5e5..8dd31cbc38 100644
--- a/packages/cli/templates/default-backend-plugin/package.json.hbs
+++ b/packages/cli/templates/default-backend-plugin/package.json.hbs
@@ -31,7 +31,6 @@
"@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}",
"@backstage/backend-defaults": "{{versionQuery '@backstage/backend-defaults'}}",
"@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}",
- "@backstage/config": "{{versionQuery '@backstage/config'}}",
"express": "{{versionQuery 'express' '4.17.1'}}",
"express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}",
"node-fetch": "{{versionQuery 'node-fetch' '2.6.7'}}"
diff --git a/packages/cli/templates/default-backend-plugin/src/service/router.ts b/packages/cli/templates/default-backend-plugin/src/service/router.ts
index 312d7e252e..ced654aa82 100644
--- a/packages/cli/templates/default-backend-plugin/src/service/router.ts
+++ b/packages/cli/templates/default-backend-plugin/src/service/router.ts
@@ -1,12 +1,11 @@
import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';
-import { LoggerService } from '@backstage/backend-plugin-api';
-import { Config } from '@backstage/config';
+import { LoggerService, RootConfigService } from '@backstage/backend-plugin-api';
import express from 'express';
import Router from 'express-promise-router';
export interface RouterOptions {
logger: LoggerService;
- config: Config;
+ config: RootConfigService;
}
export async function createRouter(
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index fb048e7237..f19fa29e29 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/config-loader
+## 1.8.2-next.0
+
+### Patch Changes
+
+- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and
+ `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource
+ for the default app-config.yaml configuration file if it's not present.
+- Updated dependencies
+ - @backstage/cli-common@0.1.14
+ - @backstage/config@1.2.0
+ - @backstage/errors@1.2.4
+ - @backstage/types@1.1.1
+
## 1.8.1
### Patch Changes
diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md
index bc8f217bbb..1fef972089 100644
--- a/packages/config-loader/api-report.md
+++ b/packages/config-loader/api-report.md
@@ -21,6 +21,7 @@ export type AsyncConfigSourceGenerator = AsyncGenerator<
// @public
export interface BaseConfigSourcesOptions {
+ allowMissingDefaultConfig?: boolean;
// (undocumented)
remote?: Pick;
// (undocumented)
@@ -87,7 +88,7 @@ export interface ConfigSourcesDefaultOptions extends BaseConfigSourcesOptions {
// (undocumented)
argv?: string[];
// (undocumented)
- env?: Record;
+ env?: Record;
}
// @public
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 28718b8290..16ff3e4f0b 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/config-loader",
- "version": "1.8.1",
+ "version": "1.8.2-next.0",
"description": "Config loading functionality used by Backstage backend, and CLI",
"backstage": {
"role": "node-library"
diff --git a/packages/config-loader/src/sources/ConfigSources.test.ts b/packages/config-loader/src/sources/ConfigSources.test.ts
index 4b14572666..e19ac1d51f 100644
--- a/packages/config-loader/src/sources/ConfigSources.test.ts
+++ b/packages/config-loader/src/sources/ConfigSources.test.ts
@@ -69,13 +69,18 @@ describe('ConfigSources', () => {
});
it('should create default sources for targets', () => {
+ const fsSpy = jest.spyOn(fs, 'pathExistsSync').mockImplementation(path => {
+ return path === `${root}app-config.yaml`;
+ });
+
expect(
mergeSources(
ConfigSources.defaultForTargets({ rootDir: '/', targets: [] }),
),
).toEqual([{ name: 'FileConfigSource', path: `${root}app-config.yaml` }]);
- const fsSpy = jest.spyOn(fs, 'pathExistsSync').mockReturnValue(true);
+ fsSpy.mockReturnValue(true);
+
expect(
mergeSources(
ConfigSources.defaultForTargets({ rootDir: '/', targets: [] }),
@@ -159,6 +164,10 @@ describe('ConfigSources', () => {
});
it('should create a default source', () => {
+ const fsSpy = jest.spyOn(fs, 'pathExistsSync').mockImplementation(path => {
+ return path === `${root}app-config.yaml`;
+ });
+
expect(
mergeSources(
ConfigSources.default({
@@ -184,6 +193,8 @@ describe('ConfigSources', () => {
{ name: 'FileConfigSource', path: resolvePath('b.yaml') },
{ name: 'EnvConfigSource', env: { HOME: '/' } },
]);
+
+ fsSpy.mockRestore();
});
it('should merge sources', () => {
diff --git a/packages/config-loader/src/sources/ConfigSources.ts b/packages/config-loader/src/sources/ConfigSources.ts
index ecff39da2d..37c1c6b087 100644
--- a/packages/config-loader/src/sources/ConfigSources.ts
+++ b/packages/config-loader/src/sources/ConfigSources.ts
@@ -74,6 +74,11 @@ export interface BaseConfigSourcesOptions {
watch?: boolean;
rootDir?: string;
remote?: Pick;
+ /**
+ * Allow the default app-config.yaml to be missing, in which case the source
+ * will not be created.
+ */
+ allowMissingDefaultConfig?: boolean;
/**
* A custom substitution function that overrides the default one.
@@ -102,7 +107,7 @@ export interface ConfigSourcesDefaultForTargetsOptions
*/
export interface ConfigSourcesDefaultOptions extends BaseConfigSourcesOptions {
argv?: string[];
- env?: Record;
+ env?: Record;
}
/**
@@ -177,14 +182,19 @@ export class ConfigSources {
if (argSources.length === 0) {
const defaultPath = resolvePath(rootDir, 'app-config.yaml');
const localPath = resolvePath(rootDir, 'app-config.local.yaml');
+ const alwaysIncludeDefaultConfigSource =
+ !options.allowMissingDefaultConfig;
+
+ if (alwaysIncludeDefaultConfigSource || fs.pathExistsSync(defaultPath)) {
+ argSources.push(
+ FileConfigSource.create({
+ watch: options.watch,
+ path: defaultPath,
+ substitutionFunc: options.substitutionFunc,
+ }),
+ );
+ }
- argSources.push(
- FileConfigSource.create({
- watch: options.watch,
- path: defaultPath,
- substitutionFunc: options.substitutionFunc,
- }),
- );
if (fs.pathExistsSync(localPath)) {
argSources.push(
FileConfigSource.create({
diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md
index ff09befd63..26b9eb20fe 100644
--- a/packages/core-app-api/CHANGELOG.md
+++ b/packages/core-app-api/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/core-app-api
+## 1.14.1-next.0
+
+### Patch Changes
+
+- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout.
+- Updated dependencies
+ - @backstage/config@1.2.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.8
+
## 1.14.0
### Minor Changes
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index 5f297b16d5..0f78b11f98 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.0",
+ "version": "1.14.1-next.0",
"publishConfig": {
"access": "public"
},
diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts
index 92e2abffb7..941ec90d66 100644
--- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts
+++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts
@@ -153,8 +153,9 @@ export class AppIdentityProxy implements IdentityApi {
// It is fine if we do NOT worry yet about deleting cookies for OTHER backends like techdocs
const appBaseUrl = await ctx.discoveryApi.getBaseUrl('app');
try {
- await ctx.fetchApi.fetch(`${appBaseUrl}/.backstage/auth/v1/cookie`, {
+ await fetch(`${appBaseUrl}/.backstage/auth/v1/cookie`, {
method: 'DELETE',
+ credentials: 'include',
});
} catch {
// Ignore the error for those who use static serving of the frontend
diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx
index 85b6bcd684..01277a09c7 100644
--- a/packages/core-app-api/src/app/AppManager.test.tsx
+++ b/packages/core-app-api/src/app/AppManager.test.tsx
@@ -19,9 +19,12 @@ import {
MockAnalyticsApi,
renderWithEffects,
withLogCollector,
+ registerMswTestHooks,
} from '@backstage/test-utils';
-import { screen, waitFor, act } from '@testing-library/react';
+import { screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import { setupServer } from 'msw/node';
+import { rest } from 'msw';
import React, { PropsWithChildren, ReactNode } from 'react';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import {
@@ -51,6 +54,9 @@ import {
} from '@backstage/core-plugin-api/alpha';
describe('Integration Test', () => {
+ const server = setupServer();
+ registerMswTestHooks(server);
+
const noOpAnalyticsApi = createApiFactory(
analyticsApiRef,
new NoOpAnalyticsApi(),
@@ -847,6 +853,17 @@ describe('Integration Test', () => {
});
it('should clear app cookie when the user logs out', async () => {
+ const logoutSignal = jest.fn();
+ server.use(
+ rest.delete(
+ 'http://localhost:7007/app/.backstage/auth/v1/cookie',
+ (_req, res, ctx) => {
+ logoutSignal();
+ return res(ctx.status(200));
+ },
+ ),
+ );
+
const meta = global.document.createElement('meta');
meta.name = 'backstage-app-mode';
meta.content = 'protected';
@@ -901,12 +918,7 @@ describe('Integration Test', () => {
await userEvent.click(screen.getByText('Sign Out'));
});
- await waitFor(() =>
- expect(fetchApiMock.fetch).toHaveBeenCalledWith(
- 'http://localhost:7007/app/.backstage/auth/v1/cookie',
- { method: 'DELETE' },
- ),
- );
+ expect(logoutSignal).toHaveBeenCalled();
global.document.head.removeChild(meta);
});
diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md
index 829c6f41ff..43acba43f0 100644
--- a/packages/core-compat-api/CHANGELOG.md
+++ b/packages/core-compat-api/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/core-compat-api
+## 0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.6.8-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/version-bridge@1.0.8
+
## 0.2.7
### Patch Changes
diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json
index bbf131c761..dd954f3318 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.7",
+ "version": "0.2.8-next.0",
"backstage": {
"role": "web-library"
},
diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx
index b9f4e5528c..3523b0a40b 100644
--- a/packages/core-compat-api/src/collectLegacyRoutes.tsx
+++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx
@@ -101,7 +101,7 @@ function visitRouteChildren(options: {
parentExtensionId: string;
context: {
pluginId: string;
- extensions: ExtensionDefinition[];
+ extensions: ExtensionDefinition[];
getUniqueName: () => string;
discoverPlugin: (plugin: LegacyBackstagePlugin) => void;
};
@@ -154,7 +154,7 @@ export function collectLegacyRoutes(
): BackstagePlugin[] {
const pluginExtensions = new Map<
LegacyBackstagePlugin,
- ExtensionDefinition[]
+ ExtensionDefinition[]
>();
const getUniqueName = (() => {
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index 3d46390597..a2463f594e 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/core-components
+## 0.14.10-next.0
+
+### Patch Changes
+
+- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component
+- 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.9
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index 55faac9dba..a2688a0037 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.9",
+ "version": "0.14.10-next.0",
"publishConfig": {
"access": "public"
},
diff --git a/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx b/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx
index ec91208eee..754ac2ec8d 100644
--- a/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx
+++ b/packages/core-components/src/components/TabbedLayout/RoutedTabs.test.tsx
@@ -160,4 +160,17 @@ describe('RoutedTabs', () => {
rendered.queryByText('tabbed-test-content-2'),
).not.toBeInTheDocument();
});
+
+ it('should render the tabs as links', async () => {
+ const routes = [testRoute1, testRoute2, testRoute3];
+ const expectedHrefs = ['/', '/some-other-path', '/some-other-path-similar'];
+ const rendered = await renderInTestApp();
+
+ const tabs = rendered.queryAllByRole('tab');
+
+ for (const [k, v] of Object.entries(tabs)) {
+ expect(v.tagName).toBe('A');
+ expect(v).toHaveAttribute('href', expectedHrefs[Number(k)]);
+ }
+ });
});
diff --git a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx
index 2c727efe1b..b57a3782bb 100644
--- a/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx
+++ b/packages/core-components/src/components/TabbedLayout/RoutedTabs.tsx
@@ -15,15 +15,11 @@
*/
import React, { useMemo } from 'react';
import { Helmet } from 'react-helmet';
-import {
- matchRoutes,
- useNavigate,
- useParams,
- useRoutes,
-} from 'react-router-dom';
+import { matchRoutes, useParams, useRoutes } from 'react-router-dom';
import { Content } from '../../layout/Content';
import { HeaderTabs } from '../../layout/HeaderTabs';
import { SubRoute } from './types';
+import { Link } from '../Link';
export function useSelectedSubRoute(subRoutes: SubRoute[]): {
index: number;
@@ -68,35 +64,33 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): {
export function RoutedTabs(props: { routes: SubRoute[] }) {
const { routes } = props;
- const navigate = useNavigate();
+
const { index, route, element } = useSelectedSubRoute(routes);
const headerTabs = useMemo(
() =>
- routes.map(t => ({
- id: t.path,
- label: t.title,
- tabProps: t.tabProps,
- })),
+ routes.map(t => {
+ const { path, title, tabProps } = t;
+ let to = path;
+ // Remove trailing /*
+ to = to.replace(/\/\*$/, '');
+ // And remove leading / for relative navigation
+ to = to.replace(/^\//, '');
+ return {
+ id: path,
+ label: title,
+ tabProps: {
+ component: Link,
+ to,
+ ...tabProps,
+ },
+ };
+ }),
[routes],
);
- const onTabChange = (tabIndex: number) => {
- let { path } = routes[tabIndex];
- // Remove trailing /*
- path = path.replace(/\/\*$/, '');
- // And remove leading / for relative navigation
- path = path.replace(/^\//, '');
- // Note! route resolves relative to the position in the React tree,
- // not relative to current location
- navigate(path);
- };
return (
<>
-
+
{element}
diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx
index dad40c4615..5aec83bc2b 100644
--- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx
+++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx
@@ -95,13 +95,4 @@ describe('', () => {
await user.click(rendered.getByText('Docs'));
expect(mockOnChange).toHaveBeenCalledTimes(1);
});
-
- it('should render 2 nav tabs', async () => {
- const rendered = await renderInTestApp();
- const tabs = rendered.queryAllByTestId(id => id.startsWith('header-tab'));
- expect(tabs).toHaveLength(2);
- tabs.forEach(tab => {
- expect(tab.tagName.toLocaleLowerCase('en-US')).toBe('a');
- });
- });
});
diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx
index 9ee9c5525f..002c9fe1ea 100644
--- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx
+++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx
@@ -18,7 +18,6 @@ import { makeStyles } from '@material-ui/core/styles';
import TabUI, { TabProps } from '@material-ui/core/Tab';
import Tabs from '@material-ui/core/Tabs';
import React, { useCallback, useEffect, useState } from 'react';
-import { Link } from '../../components/Link';
// TODO(blam): Remove this implementation when the Tabs are ready
// This is just a temporary solution to implementing tabs for now
@@ -96,9 +95,7 @@ export function HeaderTabs(props: HeaderTabsProps) {
setSelectedTab(selectedIndex);
}
}, [selectedIndex]);
- function removeLeadingSlash(path: string) {
- return path.replace(/^\//, '');
- }
+
return (
[]) {
+function routeInfoFromExtensions(extensions: ExtensionDefinition[]) {
const plugin = createPlugin({
id: 'test',
extensions,
diff --git a/packages/frontend-app-api/src/tree/createAppTree.ts b/packages/frontend-app-api/src/tree/createAppTree.ts
index 12c7f7fc3a..ee9704c3c3 100644
--- a/packages/frontend-app-api/src/tree/createAppTree.ts
+++ b/packages/frontend-app-api/src/tree/createAppTree.ts
@@ -25,7 +25,7 @@ import { instantiateAppNodeTree } from './instantiateAppNodeTree';
/** @internal */
export interface CreateAppTreeOptions {
features: FrontendFeature[];
- builtinExtensions: Extension[];
+ builtinExtensions: Extension[];
config: Config;
}
diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts
index 3330b9add8..b4090185a3 100644
--- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts
+++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts
@@ -17,6 +17,8 @@
import {
AppNode,
Extension,
+ ExtensionInput,
+ ResolvedExtensionInput,
createExtension,
createExtensionDataRef,
createExtensionInput,
@@ -32,47 +34,28 @@ import { resolveAppTree } from './resolveAppTree';
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
import { withLogCollector } from '@backstage/test-utils';
-const testDataRef = createExtensionDataRef('test');
-const otherDataRef = createExtensionDataRef('other');
-const inputMirrorDataRef = createExtensionDataRef('mirror');
+const testDataRef = createExtensionDataRef().with({ id: 'test' });
+const otherDataRef = createExtensionDataRef().with({ id: 'other' });
+const inputMirrorDataRef = createExtensionDataRef().with({
+ id: 'mirror',
+});
-const simpleExtension = resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- output: {
- test: testDataRef,
- other: otherDataRef.optional(),
- },
- configSchema: createSchemaFromZod(z =>
- z.object({
- output: z.string().default('test'),
- other: z.number().optional(),
- }),
- ),
- factory({ config }) {
- return { test: config.output, other: config.other };
- },
- }),
-);
-
-function makeSpec(
- extension: Extension,
+function makeSpec(
+ extension: Extension,
spec?: Partial,
): AppNodeSpec {
return {
id: extension.id,
attachTo: extension.attachTo,
disabled: extension.disabled,
- extension,
+ extension: extension as Extension,
source: undefined,
...spec,
};
}
-function makeNode(
- extension: Extension,
+function makeNode(
+ extension: Extension,
spec?: Partial,
): AppNode {
return {
@@ -83,8 +66,8 @@ function makeNode(
};
}
-function makeInstanceWithId(
- extension: Extension,
+function makeInstanceWithId(
+ extension: Extension,
config?: TConfig,
): AppNode {
const node = makeNode(extension, { config });
@@ -98,78 +81,54 @@ function makeInstanceWithId(
}
describe('instantiateAppNodeTree', () => {
- it('should instantiate a single node', () => {
- const tree = resolveAppTree('root-node', [
- makeSpec(simpleExtension, { id: 'root-node' }),
- ]);
- expect(tree.root.instance).not.toBeDefined();
- instantiateAppNodeTree(tree.root);
- expect(tree.root.instance).toBeDefined();
- expect(tree.root.instance?.getData(testDataRef)).toBe('test');
-
- // Multiple calls should have no effect
- instantiateAppNodeTree(tree.root);
- expect(tree.root.instance).toBeDefined();
- });
-
- it('should not instantiate disabled nodes', () => {
- const tree = resolveAppTree('root-node', [
- makeSpec(simpleExtension, { id: 'root-node', disabled: true }),
- ]);
- expect(tree.root.instance).not.toBeDefined();
- instantiateAppNodeTree(tree.root);
- expect(tree.root.instance).not.toBeDefined();
- });
-
- it('should instantiate a node with attachments', () => {
- const tree = resolveAppTree('root-node', [
- makeSpec(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'root-node',
- attachTo: { id: 'ignored', input: 'ignored' },
- inputs: {
- test: createExtensionInput({ test: testDataRef }),
- },
- output: {
- inputMirror: inputMirrorDataRef,
- },
- factory({ inputs }) {
- return { inputMirror: inputs };
- },
+ describe('v1', () => {
+ const simpleExtension = resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: {
+ test: testDataRef,
+ other: otherDataRef.optional(),
+ },
+ configSchema: createSchemaFromZod(z =>
+ z.object({
+ output: z.string().default('test'),
+ other: z.number().optional(),
}),
),
- ),
- makeSpec(simpleExtension, {
- id: 'child-node',
- attachTo: { id: 'root-node', input: 'test' },
+ factory({ config }) {
+ return { test: config.output, other: config.other };
+ },
}),
- ]);
+ );
- const childNode = tree.nodes.get('child-node');
- expect(childNode).toBeDefined();
+ it('should instantiate a single node', () => {
+ const tree = resolveAppTree('root-node', [
+ makeSpec(simpleExtension, { id: 'root-node' }),
+ ]);
+ expect(tree.root.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(tree.root.instance?.getData(testDataRef)).toBe('test');
- expect(tree.root.instance).not.toBeDefined();
- expect(childNode?.instance).not.toBeDefined();
- instantiateAppNodeTree(tree.root);
- expect(tree.root.instance).toBeDefined();
- expect(childNode?.instance).toBeDefined();
- expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({
- test: [
- { node: { spec: { id: 'child-node' } }, output: { test: 'test' } },
- ],
+ // Multiple calls should have no effect
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
});
- // Multiple calls should have no effect
- instantiateAppNodeTree(tree.root);
- expect(tree.root.instance).toBeDefined();
- expect(childNode?.instance).toBeDefined();
- });
+ it('should not instantiate disabled nodes', () => {
+ const tree = resolveAppTree('root-node', [
+ makeSpec(simpleExtension, { id: 'root-node', disabled: true }),
+ ]);
+ expect(tree.root.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).not.toBeDefined();
+ });
- it('should not instantiate disabled attachments', () => {
- const tree = resolveAppTree('root-node', [
- {
- ...makeSpec(
+ it('should instantiate a node with attachments', () => {
+ const tree = resolveAppTree('root-node', [
+ makeSpec(
resolveExtensionDefinition(
createExtension({
namespace: 'root-node',
@@ -186,437 +145,1061 @@ describe('instantiateAppNodeTree', () => {
}),
),
),
- },
- {
- ...makeSpec(simpleExtension),
- id: 'child-node',
- // Using an invalid input should not be an error when disabled
- attachTo: { id: 'root-node', input: 'invalid' },
- disabled: true,
- },
- ]);
+ makeSpec(simpleExtension, {
+ id: 'child-node',
+ attachTo: { id: 'root-node', input: 'test' },
+ }),
+ ]);
- const childNode = tree.nodes.get('child-node');
- expect(childNode).toBeDefined();
+ const childNode = tree.nodes.get('child-node');
+ expect(childNode).toBeDefined();
- expect(tree.root.instance).not.toBeDefined();
- expect(childNode?.instance).not.toBeDefined();
- instantiateAppNodeTree(tree.root);
- expect(tree.root.instance).toBeDefined();
- expect(childNode?.instance).not.toBeDefined();
- expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({
- test: [],
- });
- });
-});
+ expect(tree.root.instance).not.toBeDefined();
+ expect(childNode?.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(childNode?.instance).toBeDefined();
+ expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({
+ test: [
+ { node: { spec: { id: 'child-node' } }, output: { test: 'test' } },
+ ],
+ });
-describe('createAppNodeInstance', () => {
- it('should create a simple extension instance', () => {
- const attachments = new Map();
- const instance = createAppNodeInstance({
- node: makeNode(simpleExtension),
- attachments,
+ // Multiple calls should have no effect
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(childNode?.instance).toBeDefined();
});
- expect(Array.from(instance.getDataRefs())).toEqual([
- testDataRef,
- otherDataRef.optional(),
- ]);
- expect(instance.getData(testDataRef)).toEqual('test');
- });
-
- it('should create an extension with different kind of inputs', () => {
- const attachments = new Map([
- [
- 'optionalSingletonPresent',
- [
- makeInstanceWithId(simpleExtension, {
- output: 'optionalSingletonPresent',
- }),
- ],
- ],
- [
- 'singleton',
- [
- makeInstanceWithId(simpleExtension, {
- output: 'singleton',
- other: 2,
- }),
- ],
- ],
- [
- 'many',
- [
- makeInstanceWithId(simpleExtension, { output: 'many1' }),
- makeInstanceWithId(simpleExtension, { output: 'many2', other: 3 }),
- ],
- ],
- ]);
- const instance = createAppNodeInstance({
- attachments,
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- inputs: {
- optionalSingletonPresent: createExtensionInput(
- {
- test: testDataRef,
- other: otherDataRef.optional(),
- },
- { singleton: true, optional: true },
- ),
- optionalSingletonMissing: createExtensionInput(
- {
- test: testDataRef,
- other: otherDataRef.optional(),
- },
- { singleton: true, optional: true },
- ),
- singleton: createExtensionInput(
- {
- test: testDataRef,
- other: otherDataRef.optional(),
- },
- { singleton: true },
- ),
- many: createExtensionInput({
- test: testDataRef,
- other: otherDataRef.optional(),
- }),
- },
- output: {
- inputMirror: inputMirrorDataRef,
- },
- factory({ inputs }) {
- return { inputMirror: inputs };
- },
- }),
- ),
- ),
- });
-
- expect(Array.from(instance.getDataRefs())).toEqual([inputMirrorDataRef]);
- expect(instance.getData(inputMirrorDataRef)).toMatchObject({
- optionalSingletonPresent: {
- node: { spec: { id: 'app/test' } },
- output: { test: 'optionalSingletonPresent' },
- },
- singleton: {
- node: { spec: { id: 'app/test' } },
- output: { test: 'singleton', other: 2 },
- },
- many: [
- { node: { spec: { id: 'app/test' } }, output: { test: 'many1' } },
+ it('should not instantiate disabled attachments', () => {
+ const tree = resolveAppTree('root-node', [
{
- node: { spec: { id: 'app/test' } },
- output: { test: 'many2', other: 3 },
+ ...makeSpec(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'root-node',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ test: createExtensionInput({ test: testDataRef }),
+ },
+ output: {
+ inputMirror: inputMirrorDataRef,
+ },
+ factory({ inputs }) {
+ return { inputMirror: inputs };
+ },
+ }),
+ ),
+ ),
},
- ],
+ {
+ ...makeSpec(simpleExtension),
+ id: 'child-node',
+ // Using an invalid input should not be an error when disabled
+ attachTo: { id: 'root-node', input: 'invalid' },
+ disabled: true,
+ },
+ ]);
+
+ const childNode = tree.nodes.get('child-node');
+ expect(childNode).toBeDefined();
+
+ expect(tree.root.instance).not.toBeDefined();
+ expect(childNode?.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(childNode?.instance).not.toBeDefined();
+ expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({
+ test: [],
+ });
+ });
+
+ describe('createAppNodeInstance', () => {
+ it('should create a simple extension instance', () => {
+ const attachments = new Map();
+ const instance = createAppNodeInstance({
+ node: makeNode(simpleExtension),
+ attachments,
+ });
+
+ expect(Array.from(instance.getDataRefs())).toEqual([
+ testDataRef,
+ otherDataRef.optional(),
+ ]);
+ expect(instance.getData(testDataRef)).toEqual('test');
+ });
+
+ it('should create an extension with different kind of inputs', () => {
+ const attachments = new Map([
+ [
+ 'optionalSingletonPresent',
+ [
+ makeInstanceWithId(simpleExtension, {
+ output: 'optionalSingletonPresent',
+ }),
+ ],
+ ],
+ [
+ 'singleton',
+ [
+ makeInstanceWithId(simpleExtension, {
+ output: 'singleton',
+ other: 2,
+ }),
+ ],
+ ],
+ [
+ 'many',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, {
+ output: 'many2',
+ other: 3,
+ }),
+ ],
+ ],
+ ]);
+ const instance = createAppNodeInstance({
+ attachments,
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ optionalSingletonPresent: createExtensionInput(
+ {
+ test: testDataRef,
+ other: otherDataRef.optional(),
+ },
+ { singleton: true, optional: true },
+ ),
+ optionalSingletonMissing: createExtensionInput(
+ {
+ test: testDataRef,
+ other: otherDataRef.optional(),
+ },
+ { singleton: true, optional: true },
+ ),
+ singleton: createExtensionInput(
+ {
+ test: testDataRef,
+ other: otherDataRef.optional(),
+ },
+ { singleton: true },
+ ),
+ many: createExtensionInput({
+ test: testDataRef,
+ other: otherDataRef.optional(),
+ }),
+ },
+ output: {
+ inputMirror: inputMirrorDataRef,
+ },
+ factory({ inputs }) {
+ return { inputMirror: inputs };
+ },
+ }),
+ ),
+ ),
+ });
+
+ expect(Array.from(instance.getDataRefs())).toEqual([
+ inputMirrorDataRef,
+ ]);
+ expect(instance.getData(inputMirrorDataRef)).toMatchObject({
+ optionalSingletonPresent: {
+ node: { spec: { id: 'app/test' } },
+ output: { test: 'optionalSingletonPresent' },
+ },
+ singleton: {
+ node: { spec: { id: 'app/test' } },
+ output: { test: 'singleton', other: 2 },
+ },
+ many: [
+ { node: { spec: { id: 'app/test' } }, output: { test: 'many1' } },
+ {
+ node: { spec: { id: 'app/test' } },
+ output: { test: 'many2', other: 3 },
+ },
+ ],
+ });
+ });
+
+ it('should refuse to create an extension with invalid config', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(simpleExtension, {
+ config: { other: 'not-a-number' },
+ }),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Invalid configuration for extension 'app/test'; caused by Error: Expected number, received string at 'other'",
+ );
+ });
+
+ it('should forward extension factory errors', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: {},
+ factory() {
+ const error = new Error('NOPE');
+ error.name = 'NopeError';
+ throw error;
+ },
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test'; caused by NopeError: NOPE",
+ );
+ });
+
+ it('should refuse to create an instance with duplicate output', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: {
+ test1: testDataRef,
+ test2: testDataRef,
+ },
+ factory({}) {
+ return { test1: 'test', test2: 'test2' };
+ },
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', duplicate extension data 'test' received via output 'test2'",
+ );
+ });
+
+ it('should refuse to create an instance with disconnected output data', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: {
+ test: testDataRef,
+ },
+ factory({}) {
+ return { nonexistent: 'test' } as any;
+ },
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', unknown output provided via 'nonexistent'",
+ );
+ });
+
+ it('should refuse to create an instance with missing required input', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput(
+ {
+ test: testDataRef,
+ },
+ { singleton: true },
+ ),
+ },
+ output: {},
+ factory: () => ({}),
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', input 'singleton' is required but was not received",
+ );
+ });
+
+ it('should warn when creating an instance with undeclared inputs', () => {
+ const { warn } = withLogCollector(['warn'], () =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'declared',
+ [
+ makeInstanceWithId(simpleExtension, {
+ output: 'many1',
+ }),
+ ],
+ ],
+ [
+ 'undeclared',
+ [
+ makeInstanceWithId(simpleExtension, {
+ output: 'many1',
+ }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'parent',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ declared: createExtensionInput({
+ test: testDataRef,
+ }),
+ },
+ output: {},
+ factory: () => ({}),
+ }),
+ ),
+ ),
+ }),
+ );
+
+ expect(warn).toEqual([
+ "The extension 'app/test' is attached to the input 'undeclared' of the extension 'app/parent', but it has no such input (candidates are 'declared')",
+ ]);
+ });
+
+ it('should refuse to create an instance with multiple undeclared inputs', () => {
+ const { warn } = withLogCollector(['warn'], () =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'undeclared1',
+ [makeInstanceWithId(simpleExtension, { output: 'many1' })],
+ ],
+ [
+ 'undeclared2',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'parent',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: {},
+ factory: () => ({}),
+ }),
+ ),
+ ),
+ }),
+ );
+
+ expect(warn).toEqual([
+ "The extension 'app/test' is attached to the input 'undeclared1' of the extension 'app/parent', but it has no inputs",
+ "The extensions 'app/test', 'app/test' are attached to the input 'undeclared2' of the extension 'app/parent', but it has no inputs",
+ ]);
+ });
+
+ it('should refuse to create an instance with multiple inputs for required singleton', () => {
+ expect(() =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'singleton',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, { output: 'many2' }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput(
+ {
+ test: testDataRef,
+ },
+ { singleton: true },
+ ),
+ },
+ output: {},
+ factory: () => ({}),
+ }),
+ ),
+ ),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', expected exactly one 'singleton' input but received multiple: 'app/test', 'app/test'",
+ );
+ });
+
+ it('should refuse to create an instance with multiple inputs for optional singleton', () => {
+ expect(() =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'singleton',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, { output: 'many2' }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput(
+ {
+ test: testDataRef,
+ },
+ { singleton: true, optional: true },
+ ),
+ },
+ output: {},
+ factory: () => ({}),
+ }),
+ ),
+ ),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', expected at most one 'singleton' input but received multiple: 'app/test', 'app/test'",
+ );
+ });
+
+ it('should refuse to create an instance with multiple inputs that did not provide required data', () => {
+ expect(() =>
+ createAppNodeInstance({
+ attachments: new Map([
+ ['singleton', [makeInstanceWithId(simpleExtension, undefined)]],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput(
+ {
+ other: otherDataRef,
+ },
+ { singleton: true },
+ ),
+ },
+ output: {},
+ factory: () => ({}),
+ }),
+ ),
+ ),
+ }),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"Failed to instantiate extension 'app/test', extension 'app/test' could not be attached because its output data ('test', 'other') does not match what the input 'singleton' requires ('other')"`,
+ );
+ });
});
});
- it('should refuse to create an extension with invalid config', () => {
- expect(() =>
- createAppNodeInstance({
- node: makeNode(simpleExtension, { config: { other: 'not-a-number' } }),
- attachments: new Map(),
- }),
- ).toThrow(
- "Invalid configuration for extension 'app/test'; caused by Error: Expected number, received string at 'other'",
- );
- });
-
- it('should forward extension factory errors', () => {
- expect(() =>
- createAppNodeInstance({
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- output: {},
- factory() {
- const error = new Error('NOPE');
- error.name = 'NopeError';
- throw error;
- },
- }),
- ),
+ describe('v2', () => {
+ const simpleExtension = resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: [testDataRef, otherDataRef.optional()],
+ configSchema: createSchemaFromZod(z =>
+ z.object({
+ output: z.string().default('test'),
+ other: z.number().optional(),
+ }),
),
- attachments: new Map(),
+ factory({ config }) {
+ return [
+ testDataRef(config.output),
+ ...(config.other ? [otherDataRef(config.other)] : []),
+ ];
+ },
}),
- ).toThrow(
- "Failed to instantiate extension 'app/test'; caused by NopeError: NOPE",
);
- });
- it('should refuse to create an instance with duplicate output', () => {
- expect(() =>
- createAppNodeInstance({
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- output: {
- test1: testDataRef,
- test2: testDataRef,
- },
- factory({}) {
- return { test1: 'test', test2: 'test2' };
- },
- }),
- ),
- ),
- attachments: new Map(),
- }),
- ).toThrow(
- "Failed to instantiate extension 'app/test', duplicate extension data 'test' received via output 'test2'",
- );
- });
-
- it('should refuse to create an instance with disconnected output data', () => {
- expect(() =>
- createAppNodeInstance({
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- output: {
- test: testDataRef,
- },
- factory({}) {
- return { nonexistent: 'test' } as any;
- },
- }),
- ),
- ),
- attachments: new Map(),
- }),
- ).toThrow(
- "Failed to instantiate extension 'app/test', unknown output provided via 'nonexistent'",
- );
- });
-
- it('should refuse to create an instance with missing required input', () => {
- expect(() =>
- createAppNodeInstance({
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- inputs: {
- singleton: createExtensionInput(
- {
- test: testDataRef,
+ function mirrorInputs(ctx: {
+ inputs: {
+ [name in string]:
+ | undefined
+ | ResolvedExtensionInput<
+ ExtensionInput
+ >
+ | Array<
+ ResolvedExtensionInput<
+ ExtensionInput
+ >
+ >;
+ };
+ }) {
+ return [
+ inputMirrorDataRef(
+ Object.fromEntries(
+ Object.entries(ctx.inputs).map(([k, v]) => [
+ k,
+ Array.isArray(v)
+ ? v.map(vi => ({
+ node: vi.node,
+ test: vi.get(testDataRef),
+ other: vi.get(otherDataRef),
+ }))
+ : {
+ node: v?.node,
+ test: v?.get(testDataRef),
+ other: v?.get(otherDataRef),
},
- { singleton: true },
- ),
+ ]),
+ ),
+ ),
+ ];
+ }
+
+ it('should instantiate a single node', () => {
+ const tree = resolveAppTree('root-node', [
+ makeSpec(simpleExtension, { id: 'root-node' }),
+ ]);
+ expect(tree.root.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(tree.root.instance?.getData(testDataRef)).toBe('test');
+
+ // Multiple calls should have no effect
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ });
+
+ it('should not instantiate disabled nodes', () => {
+ const tree = resolveAppTree('root-node', [
+ makeSpec(simpleExtension, { id: 'root-node', disabled: true }),
+ ]);
+ expect(tree.root.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).not.toBeDefined();
+ });
+
+ it('should instantiate a node with attachments', () => {
+ const tree = resolveAppTree('root-node', [
+ makeSpec(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'root-node',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ test: createExtensionInput([testDataRef]),
},
- output: {},
- factory: () => ({}),
+ output: [inputMirrorDataRef],
+ factory: mirrorInputs,
}),
),
),
- attachments: new Map(),
- }),
- ).toThrow(
- "Failed to instantiate extension 'app/test', input 'singleton' is required but was not received",
- );
- });
+ makeSpec(simpleExtension, {
+ id: 'child-node',
+ attachTo: { id: 'root-node', input: 'test' },
+ }),
+ ]);
- it('should warn when creating an instance with undeclared inputs', () => {
- const { warn } = withLogCollector(['warn'], () =>
- createAppNodeInstance({
- attachments: new Map([
+ const childNode = tree.nodes.get('child-node');
+ expect(childNode).toBeDefined();
+
+ expect(tree.root.instance).not.toBeDefined();
+ expect(childNode?.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(childNode?.instance).toBeDefined();
+ expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({
+ test: [{ node: { spec: { id: 'child-node' } }, test: 'test' }],
+ });
+
+ // Multiple calls should have no effect
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(childNode?.instance).toBeDefined();
+ });
+
+ it('should not instantiate disabled attachments', () => {
+ const tree = resolveAppTree('root-node', [
+ {
+ ...makeSpec(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'root-node',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ test: createExtensionInput([testDataRef]),
+ },
+ output: [inputMirrorDataRef],
+ factory: mirrorInputs,
+ }),
+ ),
+ ),
+ },
+ {
+ ...makeSpec(simpleExtension),
+ id: 'child-node',
+ // Using an invalid input should not be an error when disabled
+ attachTo: { id: 'root-node', input: 'invalid' },
+ disabled: true,
+ },
+ ]);
+
+ const childNode = tree.nodes.get('child-node');
+ expect(childNode).toBeDefined();
+
+ expect(tree.root.instance).not.toBeDefined();
+ expect(childNode?.instance).not.toBeDefined();
+ instantiateAppNodeTree(tree.root);
+ expect(tree.root.instance).toBeDefined();
+ expect(childNode?.instance).not.toBeDefined();
+ expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({
+ test: [],
+ });
+ });
+
+ describe('createAppNodeInstance', () => {
+ it('should create a simple extension instance', () => {
+ const attachments = new Map();
+ const instance = createAppNodeInstance({
+ node: makeNode(simpleExtension),
+ attachments,
+ });
+
+ expect(Array.from(instance.getDataRefs())).toEqual([testDataRef]);
+ expect(instance.getData(testDataRef)).toEqual('test');
+ });
+
+ it('should create an extension with different kind of inputs', () => {
+ const attachments = new Map([
[
- 'declared',
+ 'optionalSingletonPresent',
[
makeInstanceWithId(simpleExtension, {
- output: 'many1',
+ output: 'optionalSingletonPresent',
}),
],
],
[
- 'undeclared',
+ 'singleton',
[
makeInstanceWithId(simpleExtension, {
- output: 'many1',
+ output: 'singleton',
+ other: 2,
}),
],
],
- ]),
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'parent',
- attachTo: { id: 'ignored', input: 'ignored' },
- inputs: {
- declared: createExtensionInput({
- test: testDataRef,
+ [
+ 'many',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, {
+ output: 'many2',
+ other: 3,
+ }),
+ ],
+ ],
+ ]);
+
+ const instance = createAppNodeInstance({
+ attachments,
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ optionalSingletonPresent: createExtensionInput(
+ [testDataRef, otherDataRef.optional()],
+ { singleton: true, optional: true },
+ ),
+ optionalSingletonMissing: createExtensionInput(
+ [testDataRef, otherDataRef.optional()],
+ { singleton: true, optional: true },
+ ),
+ singleton: createExtensionInput(
+ [testDataRef, otherDataRef.optional()],
+ { singleton: true },
+ ),
+ many: createExtensionInput([
+ testDataRef,
+ otherDataRef.optional(),
+ ]),
+ },
+ output: [inputMirrorDataRef],
+ factory: mirrorInputs,
+ }),
+ ),
+ ),
+ });
+
+ expect(Array.from(instance.getDataRefs())).toEqual([
+ inputMirrorDataRef,
+ ]);
+ expect(instance.getData(inputMirrorDataRef)).toMatchObject({
+ optionalSingletonPresent: {
+ node: { spec: { id: 'app/test' } },
+ test: 'optionalSingletonPresent',
+ },
+ singleton: {
+ node: { spec: { id: 'app/test' } },
+ test: 'singleton',
+ other: 2,
+ },
+ many: [
+ { node: { spec: { id: 'app/test' } }, test: 'many1' },
+ {
+ node: { spec: { id: 'app/test' } },
+ test: 'many2',
+ other: 3,
+ },
+ ],
+ });
+ });
+
+ it('should refuse to create an extension with invalid config', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(simpleExtension, {
+ config: { other: 'not-a-number' },
+ }),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Invalid configuration for extension 'app/test'; caused by Error: Expected number, received string at 'other'",
+ );
+ });
+
+ it('should forward extension factory errors', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: [],
+ factory() {
+ const error = new Error('NOPE');
+ error.name = 'NopeError';
+ throw error;
+ },
}),
- },
- output: {},
- factory: () => ({}),
- }),
- ),
- ),
- }),
- );
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test'; caused by NopeError: NOPE",
+ );
+ });
- expect(warn).toEqual([
- "The extension 'app/test' is attached to the input 'undeclared' of the extension 'app/parent', but it has no such input (candidates are 'declared')",
- ]);
- });
-
- it('should refuse to create an instance with multiple undeclared inputs', () => {
- const { warn } = withLogCollector(['warn'], () =>
- createAppNodeInstance({
- attachments: new Map([
- [
- 'undeclared1',
- [makeInstanceWithId(simpleExtension, { output: 'many1' })],
- ],
- [
- 'undeclared2',
- [
- makeInstanceWithId(simpleExtension, { output: 'many1' }),
- makeInstanceWithId(simpleExtension, { output: 'many1' }),
- ],
- ],
- ]),
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'parent',
- attachTo: { id: 'ignored', input: 'ignored' },
- output: {},
- factory: () => ({}),
- }),
- ),
- ),
- }),
- );
-
- expect(warn).toEqual([
- "The extension 'app/test' is attached to the input 'undeclared1' of the extension 'app/parent', but it has no inputs",
- "The extensions 'app/test', 'app/test' are attached to the input 'undeclared2' of the extension 'app/parent', but it has no inputs",
- ]);
- });
-
- it('should refuse to create an instance with multiple inputs for required singleton', () => {
- expect(() =>
- createAppNodeInstance({
- attachments: new Map([
- [
- 'singleton',
- [
- makeInstanceWithId(simpleExtension, { output: 'many1' }),
- makeInstanceWithId(simpleExtension, { output: 'many2' }),
- ],
- ],
- ]),
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- inputs: {
- singleton: createExtensionInput(
- {
- test: testDataRef,
+ it('should refuse to create an instance with duplicate output', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: [testDataRef, testDataRef],
+ factory({}) {
+ return [testDataRef('test'), testDataRef('test2')];
},
- { singleton: true },
- ),
- },
- output: {},
- factory: () => ({}),
- }),
- ),
- ),
- }),
- ).toThrow(
- "Failed to instantiate extension 'app/test', expected exactly one 'singleton' input but received multiple: 'app/test', 'app/test'",
- );
- });
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', duplicate extension data output 'test'",
+ );
+ });
- it('should refuse to create an instance with multiple inputs for optional singleton', () => {
- expect(() =>
- createAppNodeInstance({
- attachments: new Map([
- [
- 'singleton',
- [
- makeInstanceWithId(simpleExtension, { output: 'many1' }),
- makeInstanceWithId(simpleExtension, { output: 'many2' }),
- ],
- ],
- ]),
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- inputs: {
- singleton: createExtensionInput(
- {
- test: testDataRef,
+ it('should refuse to create an instance without required', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: [testDataRef],
+ factory({}) {
+ return [] as any;
},
- { singleton: true, optional: true },
- ),
- },
- output: {},
- factory: () => ({}),
- }),
- ),
- ),
- }),
- ).toThrow(
- "Failed to instantiate extension 'app/test', expected at most one 'singleton' input but received multiple: 'app/test', 'app/test'",
- );
- });
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', missing required extension data output 'test'",
+ );
+ });
- it('should refuse to create an instance with multiple inputs that did not provide required data', () => {
- expect(() =>
- createAppNodeInstance({
- attachments: new Map([
- ['singleton', [makeInstanceWithId(simpleExtension, undefined)]],
- ]),
- node: makeNode(
- resolveExtensionDefinition(
- createExtension({
- namespace: 'app',
- name: 'test',
- attachTo: { id: 'ignored', input: 'ignored' },
- inputs: {
- singleton: createExtensionInput(
- {
- other: otherDataRef,
+ it('should refuse to create an instance with unknown output data', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: [],
+ factory({}) {
+ return [testDataRef('test')] as any;
},
- { singleton: true },
- ),
- },
- output: {},
- factory: () => ({}),
- }),
- ),
- ),
- }),
- ).toThrowErrorMatchingInlineSnapshot(
- `"Failed to instantiate extension 'app/test', extension 'app/test' could not be attached because its output data ('test', 'other') does not match what the input 'singleton' requires ('other')"`,
- );
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', unexpected output 'test'",
+ );
+ });
+
+ it('should refuse to create an instance with missing required input', () => {
+ expect(() =>
+ createAppNodeInstance({
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput([testDataRef], {
+ singleton: true,
+ }),
+ },
+ output: [],
+ factory: () => [],
+ }),
+ ),
+ ),
+ attachments: new Map(),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', input 'singleton' is required but was not received",
+ );
+ });
+
+ it('should warn when creating an instance with undeclared inputs', () => {
+ const { warn } = withLogCollector(['warn'], () =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'declared',
+ [
+ makeInstanceWithId(simpleExtension, {
+ output: 'many1',
+ }),
+ ],
+ ],
+ [
+ 'undeclared',
+ [
+ makeInstanceWithId(simpleExtension, {
+ output: 'many1',
+ }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'parent',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ declared: createExtensionInput([testDataRef]),
+ },
+ output: [],
+ factory: () => [],
+ }),
+ ),
+ ),
+ }),
+ );
+
+ expect(warn).toEqual([
+ "The extension 'app/test' is attached to the input 'undeclared' of the extension 'app/parent', but it has no such input (candidates are 'declared')",
+ ]);
+ });
+
+ it('should refuse to create an instance with multiple undeclared inputs', () => {
+ const { warn } = withLogCollector(['warn'], () =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'undeclared1',
+ [makeInstanceWithId(simpleExtension, { output: 'many1' })],
+ ],
+ [
+ 'undeclared2',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'parent',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ output: [],
+ factory: () => [],
+ }),
+ ),
+ ),
+ }),
+ );
+
+ expect(warn).toEqual([
+ "The extension 'app/test' is attached to the input 'undeclared1' of the extension 'app/parent', but it has no inputs",
+ "The extensions 'app/test', 'app/test' are attached to the input 'undeclared2' of the extension 'app/parent', but it has no inputs",
+ ]);
+ });
+
+ it('should refuse to create an instance with multiple inputs for required singleton', () => {
+ expect(() =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'singleton',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, { output: 'many2' }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput([testDataRef], {
+ singleton: true,
+ }),
+ },
+ output: [],
+ factory: () => [],
+ }),
+ ),
+ ),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', expected exactly one 'singleton' input but received multiple: 'app/test', 'app/test'",
+ );
+ });
+
+ it('should refuse to create an instance with multiple inputs for optional singleton', () => {
+ expect(() =>
+ createAppNodeInstance({
+ attachments: new Map([
+ [
+ 'singleton',
+ [
+ makeInstanceWithId(simpleExtension, { output: 'many1' }),
+ makeInstanceWithId(simpleExtension, { output: 'many2' }),
+ ],
+ ],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput([testDataRef], {
+ singleton: true,
+ optional: true,
+ }),
+ },
+ output: [],
+ factory: () => [],
+ }),
+ ),
+ ),
+ }),
+ ).toThrow(
+ "Failed to instantiate extension 'app/test', expected at most one 'singleton' input but received multiple: 'app/test', 'app/test'",
+ );
+ });
+
+ it('should refuse to create an instance with multiple inputs that did not provide required data', () => {
+ expect(() =>
+ createAppNodeInstance({
+ attachments: new Map([
+ ['singleton', [makeInstanceWithId(simpleExtension, undefined)]],
+ ]),
+ node: makeNode(
+ resolveExtensionDefinition(
+ createExtension({
+ namespace: 'app',
+ name: 'test',
+ attachTo: { id: 'ignored', input: 'ignored' },
+ inputs: {
+ singleton: createExtensionInput([otherDataRef], {
+ singleton: true,
+ }),
+ },
+ output: [],
+ factory: () => [],
+ }),
+ ),
+ ),
+ }),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"Failed to instantiate extension 'app/test', extension 'app/test' could not be attached because its output data ('test') does not match what the input 'singleton' requires ('other')"`,
+ );
+ });
+ });
});
});
diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts
index 63eca749b7..65626e0be1 100644
--- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts
+++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts
@@ -16,8 +16,11 @@
import {
AnyExtensionDataMap,
+ AnyExtensionDataRef,
AnyExtensionInputMap,
+ ExtensionDataContainer,
ExtensionDataRef,
+ ExtensionInput,
ResolvedExtensionInputs,
} from '@backstage/frontend-plugin-api';
import mapValues from 'lodash/mapValues';
@@ -29,7 +32,7 @@ type Mutable = {
-readonly [P in keyof T]: T[P];
};
-function resolveInputData(
+function resolveInputDataMap(
dataMap: AnyExtensionDataMap,
attachment: AppNode,
inputName: string,
@@ -54,35 +57,76 @@ function resolveInputData(
});
}
-function resolveInputs(
+function resolveInputDataContainer(
+ extensionData: Array,
+ attachment: AppNode,
+ inputName: string,
+): { node: AppNode } & ExtensionDataContainer {
+ const dataMap = new Map();
+
+ for (const ref of extensionData) {
+ if (dataMap.has(ref.id)) {
+ throw new Error(`Unexpected duplicate input data '${ref.id}'`);
+ }
+ const value = attachment.instance?.getData(ref);
+ if (value === undefined && !ref.config.optional) {
+ const expected = extensionData
+ .filter(r => !r.config.optional)
+ .map(r => `'${r.id}'`)
+ .join(', ');
+
+ const provided = [...(attachment.instance?.getDataRefs() ?? [])]
+ .map(r => `'${r.id}'`)
+ .join(', ');
+
+ throw new Error(
+ `extension '${attachment.spec.id}' could not be attached because its output data (${provided}) does not match what the input '${inputName}' requires (${expected})`,
+ );
+ }
+
+ dataMap.set(ref.id, value);
+ }
+
+ return {
+ node: attachment,
+ get(ref) {
+ return dataMap.get(ref.id);
+ },
+ } as { node: AppNode } & ExtensionDataContainer;
+}
+
+function reportUndeclaredAttachments(
id: string,
- inputMap: AnyExtensionInputMap,
+ inputMap: { [name in string]: unknown },
attachments: ReadonlyMap,
-): ResolvedExtensionInputs {
+) {
const undeclaredAttachments = Array.from(attachments.entries()).filter(
([inputName]) => inputMap[inputName] === undefined,
);
- if (process.env.NODE_ENV !== 'production') {
- const inputNames = Object.keys(inputMap);
+ const inputNames = Object.keys(inputMap);
- for (const [name, nodes] of undeclaredAttachments) {
- const pl = nodes.length > 1;
- // eslint-disable-next-line no-console
- console.warn(
- [
- `The extension${pl ? 's' : ''} '${nodes
- .map(n => n.spec.id)
- .join("', '")}' ${pl ? 'are' : 'is'}`,
- `attached to the input '${name}' of the extension '${id}', but it`,
- inputNames.length === 0
- ? 'has no inputs'
- : `has no such input (candidates are '${inputNames.join("', '")}')`,
- ].join(' '),
- );
- }
+ for (const [name, nodes] of undeclaredAttachments) {
+ const pl = nodes.length > 1;
+ // eslint-disable-next-line no-console
+ console.warn(
+ [
+ `The extension${pl ? 's' : ''} '${nodes
+ .map(n => n.spec.id)
+ .join("', '")}' ${pl ? 'are' : 'is'}`,
+ `attached to the input '${name}' of the extension '${id}', but it`,
+ inputNames.length === 0
+ ? 'has no inputs'
+ : `has no such input (candidates are '${inputNames.join("', '")}')`,
+ ].join(' '),
+ );
}
+}
+function resolveV1Inputs(
+ inputMap: AnyExtensionInputMap,
+ attachments: ReadonlyMap,
+): ResolvedExtensionInputs {
return mapValues(inputMap, (input, inputName) => {
const attachedNodes = attachments.get(inputName) ?? [];
@@ -104,7 +148,7 @@ function resolveInputs(
}
return {
node: attachedNodes[0],
- output: resolveInputData(
+ output: resolveInputDataMap(
input.extensionData,
attachedNodes[0],
inputName,
@@ -114,11 +158,62 @@ function resolveInputs(
return attachedNodes.map(attachment => ({
node: attachment,
- output: resolveInputData(input.extensionData, attachment, inputName),
+ output: resolveInputDataMap(input.extensionData, attachment, inputName),
}));
}) as ResolvedExtensionInputs;
}
+function resolveV2Inputs(
+ inputMap: {
+ [inputName in string]: ExtensionInput<
+ AnyExtensionDataRef,
+ { optional: boolean; singleton: boolean }
+ >;
+ },
+ attachments: ReadonlyMap,
+): ResolvedExtensionInputs<{
+ [inputName in string]: ExtensionInput<
+ AnyExtensionDataRef,
+ { optional: boolean; singleton: boolean }
+ >;
+}> {
+ return mapValues(inputMap, (input, inputName) => {
+ const attachedNodes = attachments.get(inputName) ?? [];
+
+ if (input.config.singleton) {
+ if (attachedNodes.length > 1) {
+ const attachedNodeIds = attachedNodes.map(e => e.spec.id);
+ throw Error(
+ `expected ${
+ input.config.optional ? 'at most' : 'exactly'
+ } one '${inputName}' input but received multiple: '${attachedNodeIds.join(
+ "', '",
+ )}'`,
+ );
+ } else if (attachedNodes.length === 0) {
+ if (input.config.optional) {
+ return undefined;
+ }
+ throw Error(`input '${inputName}' is required but was not received`);
+ }
+ return resolveInputDataContainer(
+ input.extensionData,
+ attachedNodes[0],
+ inputName,
+ );
+ }
+
+ return attachedNodes.map(attachment =>
+ resolveInputDataContainer(input.extensionData, attachment, inputName),
+ );
+ }) as ResolvedExtensionInputs<{
+ [inputName in string]: ExtensionInput<
+ AnyExtensionDataRef,
+ { optional: boolean; singleton: boolean }
+ >;
+ }>;
+}
+
/** @internal */
export function createAppNodeInstance(options: {
node: AppNode;
@@ -141,24 +236,71 @@ export function createAppNodeInstance(options: {
try {
const internalExtension = toInternalExtension(extension);
- const namedOutputs = internalExtension.factory({
- node,
- config: parsedConfig,
- inputs: resolveInputs(id, internalExtension.inputs, attachments),
- });
+ if (process.env.NODE_ENV !== 'production') {
+ reportUndeclaredAttachments(id, internalExtension.inputs, attachments);
+ }
- for (const [name, output] of Object.entries(namedOutputs)) {
- const ref = internalExtension.output[name];
- if (!ref) {
- throw new Error(`unknown output provided via '${name}'`);
+ if (internalExtension.version === 'v1') {
+ const namedOutputs = internalExtension.factory({
+ node,
+ config: parsedConfig,
+ inputs: resolveV1Inputs(internalExtension.inputs, attachments),
+ });
+
+ for (const [name, output] of Object.entries(namedOutputs)) {
+ const ref = internalExtension.output[name];
+ if (!ref) {
+ throw new Error(`unknown output provided via '${name}'`);
+ }
+ if (extensionData.has(ref.id)) {
+ throw new Error(
+ `duplicate extension data '${ref.id}' received via output '${name}'`,
+ );
+ }
+ extensionData.set(ref.id, output);
+ extensionDataRefs.add(ref);
}
- if (extensionData.has(ref.id)) {
+ } else if (internalExtension.version === 'v2') {
+ const outputDataValues = internalExtension.factory({
+ node,
+ config: parsedConfig,
+ inputs: resolveV2Inputs(internalExtension.inputs, attachments),
+ });
+
+ const outputDataMap = new Map();
+ for (const value of outputDataValues) {
+ if (outputDataMap.has(value.id)) {
+ throw new Error(`duplicate extension data output '${value.id}'`);
+ }
+ outputDataMap.set(value.id, value.value);
+ }
+
+ for (const ref of internalExtension.output) {
+ const value = outputDataMap.get(ref.id);
+ outputDataMap.delete(ref.id);
+ if (value === undefined) {
+ if (!ref.config.optional) {
+ throw new Error(
+ `missing required extension data output '${ref.id}'`,
+ );
+ }
+ } else {
+ extensionData.set(ref.id, value);
+ extensionDataRefs.add(ref);
+ }
+ }
+
+ if (outputDataMap.size > 0) {
throw new Error(
- `duplicate extension data '${ref.id}' received via output '${name}'`,
+ `unexpected output '${Array.from(outputDataMap.keys()).join(
+ "', '",
+ )}'`,
);
}
- extensionData.set(ref.id, output);
- extensionDataRefs.add(ref);
+ } else {
+ throw new Error(
+ `unexpected extension version '${(internalExtension as any).version}'`,
+ );
}
} catch (e) {
throw new Error(
diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts
index f964fceb50..d9b65555fe 100644
--- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts
+++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { createExtension } from '@backstage/frontend-plugin-api';
+import { createExtension, Extension } from '@backstage/frontend-plugin-api';
import { resolveAppTree } from './resolveAppTree';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
@@ -26,7 +26,7 @@ const extension = resolveExtensionDefinition(
output: {},
factory: () => ({}),
}),
-);
+) as Extension;
const baseSpec = {
extension,
diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx
index cf6ea6dab4..d82d0ca782 100644
--- a/packages/frontend-app-api/src/wiring/createApp.tsx
+++ b/packages/frontend-app-api/src/wiring/createApp.tsx
@@ -25,6 +25,7 @@ import {
createThemeExtension,
createTranslationExtension,
FrontendFeature,
+ IconBundleBlueprint,
iconsApiRef,
RouteResolutionApi,
routeResolutionApiRef,
@@ -171,6 +172,7 @@ export interface CreateAppFeatureLoader {
/** @public */
export function createApp(options?: {
+ /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */
icons?: { [key in string]: IconComponent };
features?: (FrontendFeature | CreateAppFeatureLoader)[];
configLoader?: () => Promise<{ config: ConfigApi }>;
@@ -246,6 +248,7 @@ export function createApp(options?: {
* @public
*/
export function createSpecializedApp(options?: {
+ /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */
icons?: { [key in string]: IconComponent };
features?: FrontendFeature[];
config?: ConfigApi;
@@ -373,6 +376,11 @@ function createApiHolder(
(x): x is typeof createTranslationExtension.translationDataRef.T => !!x,
) ?? [];
+ const extensionIcons = tree.root.edges.attachments
+ .get('icons')
+ ?.map(e => e.instance?.getData(IconBundleBlueprint.dataRefs.icons))
+ .reduce((acc, bundle) => ({ ...acc, ...bundle }), {});
+
for (const factory of pluginApis) {
factoryRegistry.register('default', factory);
}
@@ -413,7 +421,8 @@ function createApiHolder(
factoryRegistry.register('static', {
api: iconsApiRef,
deps: {},
- factory: () => new DefaultIconsApi({ ...defaultIcons, ...icons }),
+ factory: () =>
+ new DefaultIconsApi({ ...defaultIcons, ...extensionIcons, ...icons }),
});
factoryRegistry.register('static', {
diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md
index 4c0e6fe9f8..607883d5e0 100644
--- a/packages/frontend-plugin-api/CHANGELOG.md
+++ b/packages/frontend-plugin-api/CHANGELOG.md
@@ -1,5 +1,76 @@
# @backstage/frontend-plugin-api
+## 0.6.8-next.0
+
+### Patch Changes
+
+- 4e53ad6: Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint`
+
+ This allows the creation of extension instances with the following pattern:
+
+ ```tsx
+ // create the extension blueprint which is used to create instances
+ const EntityCardBlueprint = createExtensionBlueprint({
+ kind: 'entity-card',
+ attachTo: { id: 'test', input: 'default' },
+ output: {
+ element: coreExtensionData.reactElement,
+ },
+ factory(params: { text: string }) {
+ return {
+ element: {params.text}
,
+ };
+ },
+ });
+
+ // create an instance of the extension blueprint with params
+ const testExtension = EntityCardBlueprint.make({
+ name: 'foo',
+ params: {
+ text: 'Hello World',
+ },
+ });
+ ```
+
+- 9b89b82: The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path.
+- 7777b5f: Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons.
+
+ ```tsx
+ import { IconBundleBlueprint } from '@backstage/frontend-plugin-api';
+
+ const exampleIconBundle = IconBundleBlueprint.make({
+ name: 'example-bundle',
+ params: {
+ icons: {
+ user: MyOwnUserIcon,
+ },
+ },
+ });
+ ```
+
+- 31bfc44: Extension data references can now be defined in a way that encapsulates the ID string in the type, in addition to the data type itself. The old way of creating extension data references is deprecated and will be removed in a future release.
+
+ For example, the following code:
+
+ ```ts
+ export const myExtension =
+ createExtensionDataRef('my-plugin.my-data');
+ ```
+
+ Should be updated to the following:
+
+ ```ts
+ export const myExtension = createExtensionDataRef().with({
+ id: 'my-plugin.my-data',
+ });
+ ```
+
+- Updated dependencies
+ - @backstage/core-components@0.14.10-next.0
+ - @backstage/core-plugin-api@1.9.3
+ - @backstage/types@1.1.1
+ - @backstage/version-bridge@1.0.8
+
## 0.6.7
### Patch Changes
diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md
index 29c165aaaa..d37f0a3a56 100644
--- a/packages/frontend-plugin-api/api-report.md
+++ b/packages/frontend-plugin-api/api-report.md
@@ -147,19 +147,23 @@ export { AnyApiFactory };
export { AnyApiRef };
-// @public (undocumented)
+// @public @deprecated (undocumented)
export type AnyExtensionDataMap = {
- [name in string]: ExtensionDataRef<
- unknown,
- {
- optional?: true;
- }
- >;
+ [name in string]: AnyExtensionDataRef;
};
// @public (undocumented)
+export type AnyExtensionDataRef = ExtensionDataRef<
+ unknown,
+ string,
+ {
+ optional?: true;
+ }
+>;
+
+// @public @deprecated (undocumented)
export type AnyExtensionInputMap = {
- [inputName in string]: ExtensionInput<
+ [inputName in string]: LegacyExtensionInput<
AnyExtensionDataMap,
{
optional: boolean;
@@ -229,7 +233,7 @@ export interface AppNodeSpec {
// (undocumented)
readonly disabled: boolean;
// (undocumented)
- readonly extension: Extension;
+ readonly extension: Extension;
// (undocumented)
readonly id: string;
// (undocumented)
@@ -318,13 +322,17 @@ export { configApiRef };
// @public (undocumented)
export interface ConfigurableExtensionDataRef<
TData,
+ TId extends string,
TConfig extends {
optional?: true;
} = {},
-> extends ExtensionDataRef {
+> extends ExtensionDataRef {
+ // (undocumented)
+ (t: TData): ExtensionDataValue;
// (undocumented)
optional(): ConfigurableExtensionDataRef<
TData,
+ TId,
TData & {
optional: true;
}
@@ -347,9 +355,17 @@ export type CoreErrorBoundaryFallbackProps = {
// @public (undocumented)
export const coreExtensionData: {
- reactElement: ConfigurableExtensionDataRef;
- routePath: ConfigurableExtensionDataRef;
- routeRef: ConfigurableExtensionDataRef, {}>;
+ reactElement: ConfigurableExtensionDataRef<
+ JSX_2.Element,
+ 'core.reactElement',
+ {}
+ >;
+ routePath: ConfigurableExtensionDataRef;
+ routeRef: ConfigurableExtensionDataRef<
+ RouteRef,
+ 'core.routing.ref',
+ {}
+ >;
};
// @public (undocumented)
@@ -380,12 +396,16 @@ export function createApiExtension<
configSchema?: PortableSchema;
inputs?: TInputs;
},
-): ExtensionDefinition;
+): ExtensionDefinition;
// @public (undocumented)
export namespace createApiExtension {
const // (undocumented)
- factoryDataRef: ConfigurableExtensionDataRef;
+ factoryDataRef: ConfigurableExtensionDataRef<
+ AnyApiFactory,
+ 'core.api.factory',
+ {}
+ >;
}
export { createApiFactory };
@@ -443,6 +463,7 @@ export namespace createAppRootWrapperExtension {
React_2.ComponentType<{
children?: React_2.ReactNode;
}>,
+ 'app.root.wrapper',
{}
>;
}
@@ -471,7 +492,7 @@ export function createComponentExtension<
inputs: Expand>;
}) => ComponentType;
};
-}): ExtensionDefinition;
+}): ExtensionDefinition;
// @public (undocumented)
export namespace createComponentExtension {
@@ -481,6 +502,7 @@ export namespace createComponentExtension {
ref: ComponentRef;
impl: ComponentType;
},
+ 'core.component.component',
{}
>;
}
@@ -491,76 +513,203 @@ export function createComponentRef(options: {
}): ComponentRef;
// @public (undocumented)
+export function createExtension<
+ UOutput extends AnyExtensionDataRef,
+ TInputs extends {
+ [inputName in string]: ExtensionInput<
+ AnyExtensionDataRef,
+ {
+ optional: boolean;
+ singleton: boolean;
+ }
+ >;
+ },
+ TConfig,
+ TConfigInput,
+ TConfigSchema extends {
+ [key: string]: (zImpl: typeof z) => z.ZodType;
+ },
+ UFactoryOutput extends ExtensionDataValue,
+>(
+ options: CreateExtensionOptions<
+ UOutput,
+ TInputs,
+ TConfig,
+ TConfigInput,
+ TConfigSchema,
+ UFactoryOutput
+ >,
+): ExtensionDefinition<
+ TConfig &
+ (string extends keyof TConfigSchema
+ ? {}
+ : {
+ [key in keyof TConfigSchema]: z.infer>;
+ }),
+ TConfigInput &
+ (string extends keyof TConfigSchema
+ ? {}
+ : z.input<
+ z.ZodObject<{
+ [key in keyof TConfigSchema]: ReturnType;
+ }>
+ >)
+>;
+
+// @public @deprecated (undocumented)
export function createExtension<
TOutput extends AnyExtensionDataMap,
TInputs extends AnyExtensionInputMap,
- TConfig = never,
+ TConfig,
+ TConfigInput,
+ TConfigSchema extends {
+ [key: string]: (zImpl: typeof z) => z.ZodType;
+ },
>(
- options: CreateExtensionOptions,
-): ExtensionDefinition;
+ options: LegacyCreateExtensionOptions<
+ TOutput,
+ TInputs,
+ TConfig,
+ TConfigInput,
+ TConfigSchema
+ >,
+): ExtensionDefinition<
+ TConfig &
+ (string extends keyof TConfigSchema
+ ? {}
+ : {
+ [key in keyof TConfigSchema]: z.infer>;
+ }),
+ TConfigInput &
+ (string extends keyof TConfigSchema
+ ? {}
+ : z.input<
+ z.ZodObject<{
+ [key in keyof TConfigSchema]: ReturnType;
+ }>
+ >)
+>;
// @public
export function createExtensionBlueprint<
TParams,
- TInputs extends AnyExtensionInputMap,
- TOutput extends AnyExtensionDataMap,
- TConfig,
+ UOutput extends AnyExtensionDataRef,
+ TInputs extends {
+ [inputName in string]: ExtensionInput<
+ AnyExtensionDataRef,
+ {
+ optional: boolean;
+ singleton: boolean;
+ }
+ >;
+ },
+ UExtraOutput extends AnyExtensionDataRef,
+ TConfigSchema extends {
+ [key in string]: (zImpl: typeof z) => z.ZodType;
+ },
+ UFactoryOutput extends ExtensionDataValue,
+ TDataRefs extends {
+ [name in string]: AnyExtensionDataRef;
+ } = never,
>(
- options: CreateExtensionBlueprintOptions,
-): ExtensionBlueprint;
+ options: CreateExtensionBlueprintOptions<
+ TParams,
+ UOutput,
+ TInputs,
+ TConfigSchema,
+ UFactoryOutput,
+ TDataRefs
+ >,
+): ExtensionBlueprint<
+ TParams,
+ UOutput,
+ TInputs,
+ UExtraOutput,
+ string extends keyof TConfigSchema
+ ? {}
+ : {
+ [key in keyof TConfigSchema]: z.infer>;
+ },
+ string extends keyof TConfigSchema
+ ? {}
+ : z.input<
+ z.ZodObject<{
+ [key in keyof TConfigSchema]: ReturnType;
+ }>
+ >,
+ TDataRefs
+>;
// @public (undocumented)
-export interface CreateExtensionBlueprintOptions<
+export type CreateExtensionBlueprintOptions<
TParams,
- TInputs extends AnyExtensionInputMap,
- TOutput extends AnyExtensionDataMap,
- TConfig,
-> {
- // (undocumented)
+ 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,
+ TDataRefs extends {
+ [name in string]: AnyExtensionDataRef;
+ },
+> = {
+ kind: string;
+ namespace?: string;
attachTo: {
id: string;
input: string;
};
- // (undocumented)
- configSchema?: PortableSchema;
- // (undocumented)
disabled?: boolean;
- // (undocumented)
+ inputs?: TInputs;
+ output: Array;
+ config?: {
+ schema: TConfigSchema;
+ };
factory(
params: TParams,
context: {
node: AppNode;
- config: TConfig;
+ config: {
+ [key in keyof TConfigSchema]: z.infer>;
+ };
inputs: Expand>;
},
- ): Expand>;
- // (undocumented)
- inputs?: TInputs;
- // (undocumented)
- kind: string;
- // (undocumented)
- namespace?: string;
- // (undocumented)
- output: TOutput;
-}
+ ): Iterable;
+ dataRefs?: TDataRefs;
+} & VerifyExtensionFactoryOutput;
-// @public (undocumented)
+// @public @deprecated (undocumented)
export function createExtensionDataRef(
id: string,
-): ConfigurableExtensionDataRef;
+): ConfigurableExtensionDataRef;
// @public (undocumented)
+export function createExtensionDataRef(): {
+ with(options: {
+ id: TId;
+ }): ConfigurableExtensionDataRef;
+};
+
+// @public @deprecated (undocumented)
export function createExtensionInput<
- TExtensionData extends AnyExtensionDataMap,
+ TExtensionDataMap extends AnyExtensionDataMap,
TConfig extends {
singleton?: boolean;
optional?: boolean;
},
>(
- extensionData: TExtensionData,
+ extensionData: TExtensionDataMap,
config?: TConfig,
-): ExtensionInput<
- TExtensionData,
+): LegacyExtensionInput<
+ TExtensionDataMap,
{
singleton: TConfig['singleton'] extends true ? true : false;
optional: TConfig['optional'] extends true ? true : false;
@@ -568,37 +717,75 @@ export function createExtensionInput<
>;
// @public (undocumented)
-export interface CreateExtensionOptions<
- TOutput extends AnyExtensionDataMap,
- TInputs extends AnyExtensionInputMap,
+export function createExtensionInput<
+ UExtensionData extends ExtensionDataRef<
+ unknown,
+ string,
+ {
+ optional?: true;
+ }
+ >,
+ TConfig extends {
+ singleton?: boolean;
+ optional?: boolean;
+ },
+>(
+ extensionData: Array,
+ config?: TConfig,
+): ExtensionInput<
+ UExtensionData,
+ {
+ singleton: TConfig['singleton'] extends true ? true : false;
+ optional: TConfig['optional'] extends true ? true : false;
+ }
+>;
+
+// @public (undocumented)
+export type CreateExtensionOptions<
+ UOutput extends AnyExtensionDataRef,
+ TInputs extends {
+ [inputName in string]: ExtensionInput<
+ AnyExtensionDataRef,
+ {
+ optional: boolean;
+ singleton: boolean;
+ }
+ >;
+ },
TConfig,
-> {
- // (undocumented)
+ TConfigInput,
+ TConfigSchema extends {
+ [key: string]: (zImpl: typeof z) => z.ZodType;
+ },
+ UFactoryOutput extends ExtensionDataValue,
+> = {
+ kind?: string;
+ namespace?: string;
+ name?: string;
attachTo: {
id: string;
input: string;
};
- // (undocumented)
- configSchema?: PortableSchema;
- // (undocumented)
disabled?: boolean;
- // (undocumented)
+ inputs?: TInputs;
+ output: Array;
+ configSchema?: PortableSchema;
+ config?: {
+ schema: TConfigSchema;
+ };
factory(context: {
node: AppNode;
- config: TConfig;
+ config: TConfig &
+ (string extends keyof TConfigSchema
+ ? {}
+ : {
+ [key in keyof TConfigSchema]: z.infer<
+ ReturnType
+ >;
+ });
inputs: Expand>;
- }): Expand>;
- // (undocumented)
- inputs?: TInputs;
- // (undocumented)
- kind?: string;
- // (undocumented)
- name?: string;
- // (undocumented)
- namespace?: string;
- // (undocumented)
- output: TOutput;
-}
+ }): Iterable;
+} & VerifyExtensionFactoryOutput;
// @public (undocumented)
export function createExtensionOverrides(
@@ -638,9 +825,14 @@ export function createNavItemExtension(options: {
routeRef: RouteRef;
title: string;
icon: IconComponent_2;
-}): ExtensionDefinition<{
- title: string;
-}>;
+}): ExtensionDefinition<
+ {
+ title: string;
+ },
+ {
+ title?: string | undefined;
+ }
+>;
// @public (undocumented)
export namespace createNavItemExtension {
@@ -651,6 +843,7 @@ export namespace createNavItemExtension {
icon: IconComponent_2;
routeRef: RouteRef;
},
+ 'core.nav-item.target',
{}
>;
}
@@ -661,7 +854,7 @@ export function createNavLogoExtension(options: {
namespace?: string;
logoIcon: JSX.Element;
logoFull: JSX.Element;
-}): ExtensionDefinition;
+}): ExtensionDefinition<{}, {}>;
// @public (undocumented)
export namespace createNavLogoExtension {
@@ -671,6 +864,7 @@ export namespace createNavLogoExtension {
logoIcon?: JSX.Element | undefined;
logoFull?: JSX.Element | undefined;
},
+ 'core.nav-logo.logo-elements',
{}
>;
}
@@ -763,14 +957,15 @@ export namespace createRouterExtension {
React_2.ComponentType<{
children?: React_2.ReactNode;
}>,
+ 'app.router.wrapper',
{}
>;
}
-// @public (undocumented)
+// @public @deprecated (undocumented)
export function createSchemaFromZod(
schemaCreator: (zImpl: typeof z) => ZodSchema,
-): PortableSchema;
+): PortableSchema;
// @public (undocumented)
export function createSignInPageExtension<
@@ -797,6 +992,7 @@ export namespace createSignInPageExtension {
const // (undocumented)
componentDataRef: ConfigurableExtensionDataRef<
React_2.ComponentType,
+ 'core.sign-in-page.component',
{}
>;
}
@@ -813,19 +1009,23 @@ export function createSubRouteRef<
// @public (undocumented)
export function createThemeExtension(
theme: AppTheme,
-): ExtensionDefinition;
+): ExtensionDefinition<{}, {}>;
// @public (undocumented)
export namespace createThemeExtension {
const // (undocumented)
- themeDataRef: ConfigurableExtensionDataRef;
+ themeDataRef: ConfigurableExtensionDataRef<
+ AppTheme,
+ 'core.theme.theme',
+ {}
+ >;
}
// @public (undocumented)
export function createTranslationExtension(options: {
name?: string;
resource: TranslationResource | TranslationMessages;
-}): ExtensionDefinition;
+}): ExtensionDefinition<{}, {}>;
// @public (undocumented)
export namespace createTranslationExtension {
@@ -839,6 +1039,7 @@ export namespace createTranslationExtension {
},
boolean
>,
+ 'core.translation.translation',
{}
>;
}
@@ -862,7 +1063,7 @@ export { ErrorApiErrorContext };
export { errorApiRef };
// @public (undocumented)
-export interface Extension {
+export interface Extension {
// (undocumented)
$$type: '@backstage/Extension';
// (undocumented)
@@ -871,7 +1072,7 @@ export interface Extension {
input: string;
};
// (undocumented)
- readonly configSchema?: PortableSchema;
+ readonly configSchema?: PortableSchema;
// (undocumented)
readonly disabled: boolean;
// (undocumented)
@@ -881,40 +1082,94 @@ export interface Extension {
// @public (undocumented)
export interface ExtensionBlueprint<
TParams,
- TInputs extends AnyExtensionInputMap,
- TOutput extends AnyExtensionDataMap,
- TConfig,
+ UOutput extends AnyExtensionDataRef,
+ TInputs extends {
+ [inputName in string]: ExtensionInput<
+ AnyExtensionDataRef,
+ {
+ optional: boolean;
+ singleton: boolean;
+ }
+ >;
+ },
+ UExtraOutput extends AnyExtensionDataRef,
+ TConfig extends {
+ [key in string]: unknown;
+ },
+ TConfigInput extends {
+ [key in string]: unknown;
+ },
+ TDataRefs extends {
+ [name in string]: AnyExtensionDataRef;
+ },
> {
// (undocumented)
- make(args: {
- namespace?: string;
- name?: string;
- attachTo?: {
- id: string;
- input: string;
- };
- disabled?: boolean;
- inputs?: TInputs;
- output?: TOutput;
- configSchema?: PortableSchema;
- params: TParams;
- factory?(
- params: TParams,
- context: {
- node: AppNode;
- config: TConfig;
- inputs: Expand>;
- orignalFactory(
- params?: TParams,
- context?: {
- node?: AppNode;
- config?: TConfig;
- inputs?: Expand>;
- },
- ): Expand>;
- },
- ): Expand>;
- }): ExtensionDefinition;
+ dataRefs: TDataRefs;
+ make<
+ TExtensionConfigSchema extends {
+ [key in string]: (zImpl: typeof z) => z.ZodType;
+ },
+ UFactoryOutput extends ExtensionDataValue,
+ >(
+ args: {
+ namespace?: string;
+ name?: string;
+ attachTo?: {
+ id: string;
+ input: string;
+ };
+ disabled?: boolean;
+ inputs?: TInputs;
+ output?: Array;
+ config?: {
+ schema: TExtensionConfigSchema & {
+ [KName in keyof TConfig]?: `Error: Config key '${KName &
+ string}' is already defined in parent schema`;
+ };
+ };
+ } & (
+ | ({
+ factory(
+ originalFactory: (
+ params: TParams,
+ context?: {
+ config?: TConfig;
+ inputs?: Expand>;
+ },
+ ) => Iterable>,
+ context: {
+ node: AppNode;
+ config: TConfig & {
+ [key in keyof TExtensionConfigSchema]: z.infer<
+ ReturnType
+ >;
+ };
+ inputs: Expand>;
+ },
+ ): Iterable;
+ } & VerifyExtensionFactoryOutput<
+ UOutput & UExtraOutput,
+ UFactoryOutput
+ >)
+ | {
+ params: TParams;
+ }
+ ),
+ ): ExtensionDefinition<
+ {
+ [key in keyof TExtensionConfigSchema]: z.infer<
+ ReturnType
+ >;
+ } & TConfig,
+ z.input<
+ z.ZodObject<{
+ [key in keyof TExtensionConfigSchema]: ReturnType<
+ TExtensionConfigSchema[key]
+ >;
+ }>
+ > &
+ TConfigInput
+ >;
}
// @public (undocumented)
@@ -931,20 +1186,46 @@ export interface ExtensionBoundaryProps {
routable?: boolean;
}
+// @public (undocumented)
+export type ExtensionDataContainer =
+ {
+ get(
+ ref: ExtensionDataRef,
+ ): UExtensionData extends ExtensionDataRef
+ ? IConfig['optional'] extends true
+ ? IData | undefined
+ : IData
+ : never;
+ };
+
// @public (undocumented)
export type ExtensionDataRef<
TData,
+ TId extends string = string,
TConfig extends {
optional?: true;
} = {},
> = {
- id: string;
- T: TData;
- config: TConfig;
- $$type: '@backstage/ExtensionDataRef';
+ readonly $$type: '@backstage/ExtensionDataRef';
+ readonly id: TId;
+ readonly T: TData;
+ readonly config: TConfig;
};
-// @public
+// @public (undocumented)
+export type ExtensionDataRefToValue =
+ TDataRef extends ExtensionDataRef
+ ? ExtensionDataValue
+ : never;
+
+// @public (undocumented)
+export type ExtensionDataValue = {
+ readonly $$type: '@backstage/ExtensionDataValue';
+ readonly id: TId;
+ readonly value: TData;
+};
+
+// @public @deprecated
export type ExtensionDataValues = {
[DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends {
optional: true;
@@ -960,7 +1241,7 @@ export type ExtensionDataValues = {
};
// @public (undocumented)
-export interface ExtensionDefinition {
+export interface ExtensionDefinition {
// (undocumented)
$$type: '@backstage/ExtensionDefinition';
// (undocumented)
@@ -969,7 +1250,7 @@ export interface ExtensionDefinition {
input: string;
};
// (undocumented)
- readonly configSchema?: PortableSchema;
+ readonly configSchema?: PortableSchema;
// (undocumented)
readonly disabled: boolean;
// (undocumented)
@@ -982,7 +1263,13 @@ export interface ExtensionDefinition {
// @public (undocumented)
export interface ExtensionInput<
- TExtensionData extends AnyExtensionDataMap,
+ TExtensionData extends ExtensionDataRef<
+ unknown,
+ string,
+ {
+ optional?: true;
+ }
+ >,
TConfig extends {
singleton: boolean;
optional: boolean;
@@ -993,7 +1280,7 @@ export interface ExtensionInput<
// (undocumented)
config: TConfig;
// (undocumented)
- extensionData: TExtensionData;
+ extensionData: Array;
}
// @public (undocumented)
@@ -1005,7 +1292,7 @@ export interface ExtensionOverrides {
// @public (undocumented)
export interface ExtensionOverridesOptions {
// (undocumented)
- extensions: ExtensionDefinition[];
+ extensions: ExtensionDefinition[];
// (undocumented)
featureFlags?: FeatureFlagConfig[];
}
@@ -1051,6 +1338,49 @@ export { gitlabAuthApiRef };
export { googleAuthApiRef };
+// @public (undocumented)
+export const IconBundleBlueprint: ExtensionBlueprint<
+ {
+ icons: {
+ [x: string]: IconComponent;
+ };
+ },
+ ConfigurableExtensionDataRef<
+ {
+ [x: string]: IconComponent;
+ },
+ 'core.icons',
+ {}
+ >,
+ {
+ [x: string]: ExtensionInput<
+ AnyExtensionDataRef,
+ {
+ optional: boolean;
+ singleton: boolean;
+ }
+ >;
+ },
+ AnyExtensionDataRef,
+ {
+ icons: string;
+ test: string;
+ },
+ {
+ test: string;
+ icons?: string | undefined;
+ },
+ {
+ icons: ConfigurableExtensionDataRef<
+ {
+ [x: string]: IconComponent;
+ },
+ 'core.icons',
+ {}
+ >;
+ }
+>;
+
// @public
export type IconComponent = ComponentType<
| {
@@ -1076,6 +1406,70 @@ export { IdentityApi };
export { identityApiRef };
+// @public @deprecated (undocumented)
+export interface LegacyCreateExtensionOptions<
+ TOutput extends AnyExtensionDataMap,
+ TInputs extends AnyExtensionInputMap,
+ TConfig,
+ TConfigInput,
+ TConfigSchema extends {
+ [key: string]: (zImpl: typeof z) => z.ZodType;
+ },
+> {
+ // (undocumented)
+ attachTo: {
+ id: string;
+ input: string;
+ };
+ // (undocumented)
+ config?: {
+ schema: TConfigSchema;
+ };
+ // @deprecated (undocumented)
+ configSchema?: PortableSchema;
+ // (undocumented)
+ disabled?: boolean;
+ // (undocumented)
+ factory(context: {
+ node: AppNode;
+ config: TConfig &
+ (string extends keyof TConfigSchema
+ ? {}
+ : {
+ [key in keyof TConfigSchema]: z.infer<
+ ReturnType
+ >;
+ });
+ inputs: Expand>;
+ }): Expand>;
+ // (undocumented)
+ inputs?: TInputs;
+ // (undocumented)
+ kind?: string;
+ // (undocumented)
+ name?: string;
+ // (undocumented)
+ namespace?: string;
+ // (undocumented)
+ output: TOutput;
+}
+
+// @public @deprecated (undocumented)
+export interface LegacyExtensionInput<
+ TExtensionDataMap extends AnyExtensionDataMap,
+ TConfig extends {
+ singleton: boolean;
+ optional: boolean;
+ },
+> {
+ // (undocumented)
+ $$type: '@backstage/ExtensionInput';
+ // (undocumented)
+ config: TConfig;
+ // (undocumented)
+ extensionData: TExtensionDataMap;
+}
+
export { microsoftAuthApiRef };
export { OAuthApi };
@@ -1104,7 +1498,7 @@ export interface PluginOptions<
ExternalRoutes extends AnyExternalRoutes,
> {
// (undocumented)
- extensions?: ExtensionDefinition[];
+ extensions?: ExtensionDefinition[];
// (undocumented)
externalRoutes?: ExternalRoutes;
// (undocumented)
@@ -1116,8 +1510,8 @@ export interface PluginOptions<
}
// @public (undocumented)
-export type PortableSchema = {
- parse: (input: unknown) => TOutput;
+export type PortableSchema = {
+ parse: (input: TInput) => TOutput;
schema: JsonObject;
};
@@ -1126,25 +1520,30 @@ export { ProfileInfo };
export { ProfileInfoApi };
// @public
-export type ResolvedExtensionInput =
- {
- node: AppNode;
- output: ExtensionDataValues;
- };
+export type ResolvedExtensionInput<
+ TExtensionInput extends ExtensionInput,
+> = TExtensionInput['extensionData'] extends Array
+ ? {
+ node: AppNode;
+ } & ExtensionDataContainer
+ : TExtensionInput['extensionData'] extends AnyExtensionDataMap
+ ? {
+ node: AppNode;
+ output: ExtensionDataValues;
+ }
+ : never;
// @public
export type ResolvedExtensionInputs<
TInputs extends {
- [name in string]: ExtensionInput;
+ [name in string]: ExtensionInput | LegacyExtensionInput;
},
> = {
[InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton']
- ? Array>>
+ ? Array>>
: false extends TInputs[InputName]['config']['optional']
- ? Expand>
- : Expand<
- ResolvedExtensionInput | undefined
- >;
+ ? Expand>
+ : Expand | undefined>;
};
// @public
diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json
index f02f3fdedf..0206fece7e 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.6.7",
+ "version": "0.6.8-next.0",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts
index 6c79a16964..4b5672f894 100644
--- a/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts
+++ b/packages/frontend-plugin-api/src/apis/definitions/AppTreeApi.ts
@@ -29,7 +29,7 @@ import { BackstagePlugin, Extension, ExtensionDataRef } from '../../wiring';
export interface AppNodeSpec {
readonly id: string;
readonly attachTo: { id: string; input: string };
- readonly extension: Extension;
+ readonly extension: Extension;
readonly disabled: boolean;
readonly config?: unknown;
readonly source?: BackstagePlugin;
diff --git a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts
new file mode 100644
index 0000000000..deeabdb44c
--- /dev/null
+++ b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { IconComponent } from '../icons';
+import { createExtensionBlueprint, createExtensionDataRef } from '../wiring';
+
+const iconsDataRef = createExtensionDataRef<{
+ [key in string]: IconComponent;
+}>().with({ id: 'core.icons' });
+
+/** @public */
+export const IconBundleBlueprint = createExtensionBlueprint({
+ kind: 'icon-bundle',
+ namespace: 'app',
+ attachTo: { id: 'app', input: 'icons' },
+ output: [iconsDataRef],
+ config: {
+ schema: {
+ icons: z => z.string().default('blob'),
+ test: z => z.string(),
+ },
+ },
+ factory: (params: { icons: { [key in string]: IconComponent } }) => [
+ iconsDataRef(params.icons),
+ ],
+ dataRefs: {
+ icons: iconsDataRef,
+ },
+});
diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts
index 1680332960..93346f1ec9 100644
--- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts
+++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts
@@ -72,6 +72,7 @@ export function createApiExtension<
/** @public */
export namespace createApiExtension {
- export const factoryDataRef =
- createExtensionDataRef('core.api.factory');
+ export const factoryDataRef = createExtensionDataRef().with({
+ id: 'core.api.factory',
+ });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx
index bbc51806af..ce2eb89f25 100644
--- a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx
@@ -77,8 +77,7 @@ export function createAppRootWrapperExtension<
/** @public */
export namespace createAppRootWrapperExtension {
- export const componentDataRef =
- createExtensionDataRef>>(
- 'app.root.wrapper',
- );
+ export const componentDataRef = createExtensionDataRef<
+ ComponentType>
+ >().with({ id: 'app.root.wrapper' });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
index a1231d8353..a40ee3b069 100644
--- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx
@@ -92,5 +92,5 @@ export namespace createComponentExtension {
export const componentDataRef = createExtensionDataRef<{
ref: ComponentRef;
impl: ComponentType;
- }>('core.component.component');
+ }>().with({ id: 'core.component.component' });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx
index 4b92d4dace..2a2c47835a 100644
--- a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx
@@ -61,5 +61,5 @@ export namespace createNavItemExtension {
title: string;
icon: IconComponent;
routeRef: RouteRef;
- }>('core.nav-item.target');
+ }>().with({ id: 'core.nav-item.target' });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx
index 3d66b26d6b..1f2419bd02 100644
--- a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx
@@ -51,5 +51,5 @@ export namespace createNavLogoExtension {
export const logoElementsDataRef = createExtensionDataRef<{
logoIcon?: JSX.Element;
logoFull?: JSX.Element;
- }>('core.nav-logo.logo-elements');
+ }>().with({ id: 'core.nav-logo.logo-elements' });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx
index 32d61f7e60..12f946e8a7 100644
--- a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx
@@ -77,8 +77,7 @@ export function createRouterExtension<
/** @public */
export namespace createRouterExtension {
- export const componentDataRef =
- createExtensionDataRef>>(
- 'app.router.wrapper',
- );
+ export const componentDataRef = createExtensionDataRef<
+ ComponentType>
+ >().with({ id: 'app.router.wrapper' });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx
index b525cc3a7b..312d30c78c 100644
--- a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx
@@ -79,5 +79,5 @@ export function createSignInPageExtension<
export namespace createSignInPageExtension {
export const componentDataRef = createExtensionDataRef<
ComponentType
- >('core.sign-in-page.component');
+ >().with({ id: 'core.sign-in-page.component' });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts
index cc6dbdabe9..1740f05a5a 100644
--- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts
+++ b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts
@@ -33,6 +33,7 @@ export function createThemeExtension(theme: AppTheme) {
/** @public */
export namespace createThemeExtension {
- export const themeDataRef =
- createExtensionDataRef('core.theme.theme');
+ export const themeDataRef = createExtensionDataRef().with({
+ id: 'core.theme.theme',
+ });
}
diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts
index 8497a64b88..9445e32e24 100644
--- a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts
+++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts
@@ -38,5 +38,5 @@ export function createTranslationExtension(options: {
export namespace createTranslationExtension {
export const translationDataRef = createExtensionDataRef<
TranslationResource | TranslationMessages
- >('core.translation.translation');
+ >().with({ id: 'core.translation.translation' });
}
diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts
index 562cb728cb..267201db81 100644
--- a/packages/frontend-plugin-api/src/extensions/index.ts
+++ b/packages/frontend-plugin-api/src/extensions/index.ts
@@ -25,3 +25,4 @@ export { createSignInPageExtension } from './createSignInPageExtension';
export { createThemeExtension } from './createThemeExtension';
export { createComponentExtension } from './createComponentExtension';
export { createTranslationExtension } from './createTranslationExtension';
+export { IconBundleBlueprint } from './IconBundleBlueprint';
diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts
index 90eba44249..477a1a58c6 100644
--- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts
+++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts
@@ -25,10 +25,19 @@ describe('createSchemaFromZod', () => {
}),
);
- expect(() => parse({ derp: { bar: 'derp' } })).toThrow(
+ expect(() => {
+ // @ts-expect-error
+ return parse({ derp: { bar: 'derp' } });
+ }).toThrow(
`Missing required value at 'foo'; Expected number, received string at 'derp.bar'`,
);
- expect(() => parse(undefined)).toThrow(`Missing required value`);
- expect(() => parse('derp')).toThrow(`Expected object, received string`);
+ expect(() => {
+ // @ts-expect-error
+ return parse(undefined);
+ }).toThrow(`Missing required value`);
+ expect(() => {
+ // @ts-expect-error
+ return parse('derp');
+ }).toThrow(`Expected object, received string`);
});
});
diff --git a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts
index bfaf57f522..9e63515ae4 100644
--- a/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts
+++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts
@@ -19,10 +19,13 @@ import { z, ZodSchema, ZodTypeDef } from 'zod';
import zodToJsonSchema from 'zod-to-json-schema';
import { PortableSchema } from './types';
-/** @public */
+/**
+ * @public
+ * @deprecated Use the `config.schema` option of `createExtension` instead, or use `createExtensionBlueprint`.
+ */
export function createSchemaFromZod(
schemaCreator: (zImpl: typeof z) => ZodSchema,
-): PortableSchema {
+): PortableSchema {
const schema = schemaCreator(z);
return {
// TODO: Types allow z.array etc here but it will break stuff
diff --git a/packages/frontend-plugin-api/src/schema/types.ts b/packages/frontend-plugin-api/src/schema/types.ts
index 9636d3b896..5644068e90 100644
--- a/packages/frontend-plugin-api/src/schema/types.ts
+++ b/packages/frontend-plugin-api/src/schema/types.ts
@@ -17,7 +17,7 @@
import { JsonObject } from '@backstage/types';
/** @public */
-export type PortableSchema = {
- parse: (input: unknown) => TOutput;
+export type PortableSchema = {
+ parse: (input: TInput) => TOutput;
schema: JsonObject;
};
diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts
index a6ff41f6b0..cb48cb8df3 100644
--- a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts
+++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts
@@ -20,7 +20,9 @@ import { createExtensionDataRef } from './createExtensionDataRef';
/** @public */
export const coreExtensionData = {
- reactElement: createExtensionDataRef