From ff176942c83b57bcac879214646175737794785a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 28 Feb 2024 10:57:31 +0100 Subject: [PATCH 001/567] docs/frontend-system: starting point for app config docs Signed-off-by: Patrik Oldsberg --- .../02-configuring-extensions.md | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/docs/frontend-system/building-apps/02-configuring-extensions.md b/docs/frontend-system/building-apps/02-configuring-extensions.md index 2fd4ce69fc..57a2110da6 100644 --- a/docs/frontend-system/building-apps/02-configuring-extensions.md +++ b/docs/frontend-system/building-apps/02-configuring-extensions.md @@ -6,4 +6,92 @@ sidebar_label: Configuring Extensions description: Documentation for how to configure extensions in a Backstage app --- -TODO +All extensions in a Backstage app can be configured through static configuration. This configuration is all done under a the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md). + +## Extension Configuration Schema + +This section focuses on the format of the `app.extensions` configuration and the various shorthands that are available. + +The most complete and verbose format for configuring an individual extensions is as follows: + +```yaml +app: + extensions: + - : + attachTo: + id: + input: + disabled: + config: +``` + +All of the top-level fields are optional: `attachTo`, `disabled`, and `config`. Every extension implementation must provide defaults for all of these fields that will be used if they are not provided in the configuration. + +Note that `app.extensions` is always an array rather than an object. For example, the following is invalid: + +```yaml title="INVALID" +app: + extensions: + : # Invalid, this should be an array item, `app.extensions` is now an object + config: ... +``` + +In addition to this schema, there are a number of shorthands available: + +Rather than a full object, you can specify just the ID of the extension as a string. This is equivalent to setting `disabled` to `false`: + +```yaml +app: + extensions: + - ‘’ +``` + +You can enable/disable individual extension by ID, in this case the value is a boolean: + +```yaml +extensions: + - : +``` + +You can override the implementation of an extension by ID, in this case the value is a string: + +```yaml +extensions: + - : ‘’ +``` + +You can **create a new extension instance with a generated ID** by including an input name in the key: + +```yaml +extensions: + - /: + extension: + config: +``` + +This syntax is only for use in the app configuration itself, every extension provided by default from a plugin must have an explicit ID. For example, the following two configurations are equivalent, except that the former does not have an explicit instance ID: + +```yaml +extensions: + # Generated ID + - core.router/routes: + extension: '@backstage/plugin-tech-radar#TechRadarPage' + # Explicit ID + - tech-radar.page: + at: core.router/routes + extension: '@backstage/plugin-tech-radar#TechRadarPage' +``` + +Lastly, if you do not need to provide additional configuration, you can combine the key input format with the implementation value format as a shorthand for creating a new extension instance with a generated ID and no configuration: + +```yaml +extensions: + - /: ‘’ +``` + +For example: + +```yaml +extensions: + - core.router/routes: '@backstage/plugin-tech-radar#TechRadarPage' +``` From faf3fdf3ada5f922976baa0bcc82be7d2f69b00e Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 28 Feb 2024 15:54:41 +0100 Subject: [PATCH 002/567] docs: add more examples to the frontend app migration guide Signed-off-by: Camila Belo --- .../building-apps/08-migrating.md | 116 +++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 98d8b3bab8..8bcc1f973f 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -117,7 +117,7 @@ You can then also add any additional extensions that you may need to create as p [Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). -For example, the following API configuration: +For example, the following apis configuration: ```ts const app = createApp({ @@ -151,15 +151,75 @@ Icons are currently installed through the usual options to `createApp`, but will Plugins are now passed through the `features` options instead. +For example, the following plugins configuration: + +```tsx +import { homePlugin } from '@backstage/plugin-home'; + +createApp({ + // ... + plugins: [homePlugin], + // ... +}); +``` + +Can be converted to the following features configuration: + +```tsx +// plugins are now default exported via alpha subpath +import homePlugin from '@backstage/plugin-home/alpha'; + +createApp({ + // ... + features: [homePlugin], + // ... +}); +``` + +Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/02-app.md#feature-discovery) is enabled. + +```yaml title="in app-config.yaml" +app: + # Enabling plugin and override features discovery + experimental: 'all' +``` + ### `featureFlags` Declaring features flags in the app is no longer supported, move these declarations to the appropriate plugins instead. +For example, the following app feature flags configuration: + +```tsx +createApp({ + // ... + featureFlags: [ + { + pluginId: '', + name: 'tech-radar', + description: 'Enables the tech radar plugin', + }, + ], + // ... +}); +``` + +Can be converted to the following plugin configuration: + +```tsx +createPlugin({ + id: 'tech-radar', + // ... + featureFlags: [{ name: 'tech-radar' }], + // ... +}); +``` + ### `components` Many app components are now installed as extensions instead using `createComponentExtension`. See the section on [configuring app components](./01-index.md#configure-your-app) for more information. -The `Router` component is now a built-in extension that you can override using `createRouterExtension`. +The `Router` component is now a built-in extension that you can [override](../architecture/05-extension-overrides.md) using `createRouterExtension`. The Sign-in page is now installed as an extension using the `createSignInPageExtension` instead. @@ -277,6 +337,35 @@ const app = createApp({ Translations are now installed as extensions, using `createTranslationExtension`. +For example, the following translations configuration: + +```tsx +import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'; +createApp({ + // ... + __experimentalTranslations: { + resources: [ + createTranslationMessages({ + ref: catalogTranslationRef, + catalog_page_create_button_title: 'Create Software', + }), + ], + }, + // ... +}); +``` + +Can be converted to the following extension: + +```tsx +createTranslationExtension({ + resource: createTranslationMessages({ + ref: catalogTranslationRef, + catalog_page_create_button_title: 'Create Software', + }), +}); +``` + ## Gradual Migration After updating all `createApp` options as well as using `convertLegacyApp` to use your existing app structure, you should be able to start up the app and see that it still works. If that is not the case, make sure you read any error messages that you may see in the app as they can provide hints on what you need to fix. If you are still stuck, you can check if anyone else ran into the same issue in our [GitHub issues](https://github.com/backstage/backstage/issues), or ask for help in our [community Discord](https://discord.gg/backstage-687207715902193673). @@ -368,7 +457,7 @@ The entity pages are typically defined in `packages/app/src/components/catalog` New apps feature a built-in sidebar extension (`app/nav`) that will render all nav item extensions provided by plugins. This is a placeholder implementation and not intended as a long-term solution. In the future we will aim to provide a more flexible sidebar extension that allows for more customization out of the box. -Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an extension override: +Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/05-extension-overrides.md): ```tsx const nav = createExtension({ @@ -435,3 +524,24 @@ export default app.createRoot( ``` Any app root wrapper needs to be migrated to be an extension, using `createAppRootWrapperExtension`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. + +Here is an example converting the `CustomAppBarrier` into extension: + +```tsx +createApp({ + // ... + features: [ + createExtensionOverrides({ + extensions: [ + createAppRootWrapperExtension({ + name: 'CustomAppBarrier', + // Whenever your component uses legacy core packages, wrap it with "compatWrapper" + // e.g. props => compatWrapper() + Component: CustomAppBarrier, + }), + ], + }), + ], + // ... +}); +``` From e2e39faa710b09ed0fa6cfebd32987d1e1b39129 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 1 Mar 2024 11:37:05 +0100 Subject: [PATCH 003/567] docs/frontend-system: removed outdated content from app config section Signed-off-by: Patrik Oldsberg --- .../02-configuring-extensions.md | 48 ++----------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/docs/frontend-system/building-apps/02-configuring-extensions.md b/docs/frontend-system/building-apps/02-configuring-extensions.md index 57a2110da6..5f090e23cd 100644 --- a/docs/frontend-system/building-apps/02-configuring-extensions.md +++ b/docs/frontend-system/building-apps/02-configuring-extensions.md @@ -49,49 +49,7 @@ app: You can enable/disable individual extension by ID, in this case the value is a boolean: ```yaml -extensions: - - : -``` - -You can override the implementation of an extension by ID, in this case the value is a string: - -```yaml -extensions: - - : ‘’ -``` - -You can **create a new extension instance with a generated ID** by including an input name in the key: - -```yaml -extensions: - - /: - extension: - config: -``` - -This syntax is only for use in the app configuration itself, every extension provided by default from a plugin must have an explicit ID. For example, the following two configurations are equivalent, except that the former does not have an explicit instance ID: - -```yaml -extensions: - # Generated ID - - core.router/routes: - extension: '@backstage/plugin-tech-radar#TechRadarPage' - # Explicit ID - - tech-radar.page: - at: core.router/routes - extension: '@backstage/plugin-tech-radar#TechRadarPage' -``` - -Lastly, if you do not need to provide additional configuration, you can combine the key input format with the implementation value format as a shorthand for creating a new extension instance with a generated ID and no configuration: - -```yaml -extensions: - - /: ‘’ -``` - -For example: - -```yaml -extensions: - - core.router/routes: '@backstage/plugin-tech-radar#TechRadarPage' +app: + extensions: + - : ``` From 6ec07d6aa16d854f0216caa80314e1f679a9d820 Mon Sep 17 00:00:00 2001 From: Waldir Date: Mon, 18 Mar 2024 14:39:55 -0500 Subject: [PATCH 004/567] BEP 0006 Describe personas and separate portal and framework docs Signed-off-by: Waldir --- .../README.md | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 beps/0006-docs-personas-framework-portal/README.md diff --git a/beps/0006-docs-personas-framework-portal/README.md b/beps/0006-docs-personas-framework-portal/README.md new file mode 100644 index 0000000000..96f9a5944a --- /dev/null +++ b/beps/0006-docs-personas-framework-portal/README.md @@ -0,0 +1,240 @@ +--- +title: Restructuring Backstage Documentation for Improved Navigation and Clarity +status: provisional +authors: + - '@waldirmontoya25' +owners: +project-areas: + - Documentation +creation-date: 2024-03-18 +--- + +# BEP: Enhancing Backstage Documentation: Personas, Framework, and Developer Portal + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/23689) + +- [BEP: Enhancing Backstage Documentation: Personas, Framework, and Developer Portal](#bep-enhancing-backstage-documentation-personas-framework-and-developer-portal) + - [Summary](#summary) + - [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) + - [Proposal](#proposal) + - [Design Details](#design-details) + - [Release Plan](#release-plan) + - [Dependencies](#dependencies) + - [Example Table of Contents](#example-table-of-contents) + +## Summary + +This BEP proposes restructuring the Backstage documentation to emphasize the dual nature of Backstage as both a framework for building developer portals and a fully functional developer portal out of the box, as demonstrated by the demo site. The documentation will be divided into two main sections: One focusing on the developer portal that users get with the core plugins, and another on the framework that allows integrators and builders to create their own developer portal. The goal is to improve clarity, navigation, and adoption of Backstage by positioning it as both a ready-to-use developer portal and a framework for building custom developer portals. + +## Motivation + +The current Backstage documentation has been reported to be difficult to navigate, making it challenging for different personas across the DevEx ecosystem to extract value. The CNCF [**assessment**](https://github.com/cncf/techdocs/tree/main/assessments/0008-backstage/) and the resulting [**issues**](https://github.com/backstage/backstage/issues/21893) highlight the need for improvement in the documentation structure. + +### Goals + +- Divide the documentation into two section: Framework and Developer Portal +- Define the personas Backstage is targeting +- Structure the documentation to cater the different personas +- Move the existing content to the appropriate section + +### Non-Goals + +- Rewrite the entire documentation from scratch +- Write additional content beyond the scope of the existing documentation + +## Proposal + +The proposed restructuring of the Backstage documentation revolves around two core ideas: + +1. Positioning Backstage as both a framework to build developer portals and a developer portal itself, and splitting the documentation into two main sections: + + - Developer Portal: Focusing on the features, configuration, and usage of the developer portal that users get out of the box with the core plugins. + - Framework: Covering the aspects of Backstage as a framework, including guides for integrators and builders who want to create their own developer portal using Backstage. + +2. Defining the personas participating in Backstage adoption journeys to improve documentation navigation. The identified personas are: + + - **User**: A person who uses Backstage to find information, use plugins, and consume the developer portal. + - **Administrator/Operator**: A person who configures, secures, and deploys the developer portal, manages plugins, and oversees the general administration of the developer portal. + - **Integrator/Builder**: A person who builds plugins, customizes the code and design, and creates custom-built developer portals based on the Backstage framework. This includes developers and designers and anyone adding new functionality to their own Backstage instance. + - **Product Manager/Business stakeholders**: A person who defines the strategy for adopting Backstage, identifies use cases, communicates the value proposition for adopting Backstage and connects the developer portal to the business strategy. + - **Contributor**: A person who contributes to the Backstage upstream ecosystem. + +The adoption strategy would be as follows: + +- Create a dedicated page describing the personas Backstage is targeting and the documentation sections that cater to each persona. +- Restructure the documentation into the two new sections (Framework and Developer Portal) and redistribute the existing content accordingly. +- Publish a blog post announcing the changes, highlighting Backstage's positioning as both a framework and a developer portal, and explaining the benefits of the restructured documentation. + +The benefits of restructuring the documentation according to these ideas include: + +1. Easier navigation and discoverability of information for different personas and use cases. +2. Clear separation of runtime and development documentation. +3. Simplified process for contributors to determine the appropriate location for new documentation. +4. Streamlined Backstage adoption process for new adopters. + +## Design Details + +- The Docs section of the microsite will be divided into two top-level sections: Framework and Developer Portal. +- The structure of the Table of Contents will align with the outline proposed in https://github.com/backstage/backstage/issues/21946. + +## Release Plan + +- Release the BEP by 03/24/2024. +- Discuss the changes with the community and gather feedback by 04/24/2024. +- Implement the changes by 04/30/2024. + +## Dependencies + +None + +## Example Table of Contents + +- Overview + - What is Backstage? + - Roadmap + - Vision + - Release and Versioning Policy + - Backstage Threat Model + - Logo assets + - Support and community +- Framework + - Architecture Overview + - Getting Started + - Integrator/Builder Guides + - Local Development + - CLI + - Linking in local packages + - Debugging Backstage + - Backstage core framework + - Systems + - Frontend + - Old + - New + - Backend + - Old + - New + - API Reference + - Tutorials + - Building plugins + - Intro to plugins + - Existing plugins + - Creating a new plugin + - Plugin development + - Structuring a plugin + - Integrating with other systems + - Integrating with the Catalog + - Integrating Search + - Composability system + - Internationalization + - Plugin analytics + - Feature flags + - OpenAPI + - Backends and APIs + - Testing + - Publishing + - Home Page + - Customizing the home page + - Software Catalog + - Extending the model + - External integrations + - Catalog Customization + - API + - Software Templates + - Writing custom actions + - Writing tests for actions + - Writing custom field extensions + - Writing custom step layouts + - Authorizing parameters, steps and actions + - Migrating to react-jsonschema-form@v5 + - Migrating to v1beta3 templates + - Search + - Overview + - Getting Started with search + - Search concepts + - Search architecture + - Search Engines + - How to Guides + - TechDocs + - Customizing TechDocs + - TechDocs add-ons + - Kubernetes + - Customizing the kubernetes plugin + - Authentication + - Proxy + - Permissions + - Overview + - Concepts + - Getting Started + - Writing a permission policy + - Frontend integration + - Defining custom permission rules + - Using permissions in plugins + - Designing for Backstage + - ADRs + - Accessibility + - References + - Contributor Guides + - Contributing to Backstage + - Reference +- Developer Portal + - Architecture Overview + - Getting Started + - Administrator Guides + - Developer Portal + - Installing and Configuring + - Database + - Authentication + - Installing plugins + - Customize the design + - Securing + - Deploying in Production + - Integrating with other systems + - Managing + - Monitoring + - Troubleshooting + - Upgrading + - Keeping backstage up to date + - Customizing + - Home Page + - Installing and Configuring + - Software Catalog + - Overview + - The life of an Entity + - Catalog Configuration + - System Model + - YAML file format + - Entity Reference + - Well Known annotations + - Well known relations + - Well known statuses + - Creating the catalog graph + - Software Templates + - Overview + - Configuring + - Adding a new template + - Writing a template + - Built in actions + - TechDocs + - Overview + - Getting Started + - Architecture + - Installing and configuring + - Using Cloud Storage for TechDocs generated files + - Configuring CI/CD to generate and publish TechDocs sites + - TechDocs CLI + - Troubleshooting + - Kubernetes + - Installing and Configuring + - Authentication + - Troubleshooting + - Search + - Product Manager Guides + - Strategies for adopting + - Use cases + - User Guides + - Logging in + - Registering a component + - Creating a new component + - Reference From 2507e3d0db9b6c05c6f9bfae94b3f787d9825338 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 19 Mar 2024 23:37:32 +0100 Subject: [PATCH 005/567] bep: add user notification settings plan Signed-off-by: Heikki Hellgren --- beps/0001-notifications-system/README.md | 31 +++++++++++++++++- .../UserNotificationSettings.png | Bin 0 -> 29081 bytes 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 beps/0001-notifications-system/UserNotificationSettings.png diff --git a/beps/0001-notifications-system/README.md b/beps/0001-notifications-system/README.md index 66e23542dc..bd3a097dea 100644 --- a/beps/0001-notifications-system/README.md +++ b/beps/0001-notifications-system/README.md @@ -380,10 +380,39 @@ interface SignalApi { - Render dynamic values with various different React elements such as the `EntityRefLink` for entity references (for example `{{ user:default/john.doe }}`) in the notification payload - Handle `link` values that use route references. For example instead hard-coding link to `/catalog/default/component/artist-web` it should be possible to use `catalogPlugin.catalogEntity` route reference as a link of the notification. This should also allow using parameters to be passed to the route reference. Links to external systems are still supported. - Add support for `analyticsApi` to notification actions like marking notifications done, saved or opening links in the notifications -- Add support for user settings to control how notifications are shown to the user and which notifications user wants to receive. This should also include support for different `NotificationProcessor`s that can send notification to external systems - Add a sound to be played when notification is received - Add i18n internationalization support for the notification payload +### User specific notification settings + +To allow the users more fine-grained control of notifications, user must have an option to unsubscribe from specific origins, topics and channels. + +By default, all notifications are enabled for the user in all notification channels. Frontend plugin provides an user interface to change these settings for example as described in the following image: + +![user notification settings UI example](./UserNotificationSettings.png) + +Each notification processor with `send` functionality will be listed as a separate channel among the Backstage internal notification channel `Web`. For this, each notification processor must implement a function to get a human readable name to be rendered in the frontend. + +User must be able to save these settings to the notification database, and they are be checked each time a new notification is sent to the user. + +Disabling a notification from specific origin or topic in the `Web` channel will not remove the old notifications being visible in the frontend from that source but instead, it only prevents new notifications being sent. + +For performance reasons the notification settings will ignore all broadcast notifications and those will be sent to all users despite their origin or channel. + +```ts +export type NotificationSetting = { + origin: string; + topic?: string; + channels: Record; +}; + +export type UserNotificationSettings = { + settings: NotificationSetting[]; +}; +``` + +The backend plugin returns user notification settings for all known origins, topics and channels it knows of. As new origins, topics and channels will get added, also the notification settings list will get longer. This allows an easy way to add new notification origins, topics and channels without need to change the notification plugin. If the user is missing notification setting from specific origin/topic/channel, the backend considers it as enabled. + ## Release Plan The notification and signal plugins are released as two new plugins in the Backstage ecosystem. They will both start out in an experimental state. diff --git a/beps/0001-notifications-system/UserNotificationSettings.png b/beps/0001-notifications-system/UserNotificationSettings.png new file mode 100644 index 0000000000000000000000000000000000000000..de9a5881b43a47c0c28778eae6c609ec20d659c0 GIT binary patch literal 29081 zcma%j1z6PE*DfF^2nqs%NOuT=APoc3J)|@!DJ=>}42{y=jYvvKNr&VRf=VMHCDJhD zkav$qJm2?!zVF`qJf7n@F!P(e*WP>WcfIesCRkNP_Qv(Q*U`|>Zph0?siUD`WTK&= z2V!3ZKRI|UXbS#9f2l4jfmS*|v4Vz1gC;NaNYmA5Jsqb}OXH+Fb4HcUT!AM6{8)EtPCbf{O0R=(dvbZ(hNC;gV)|7hA4moa4 zw@)swS05rv4u@o?8^&e^k4v3rcbf+9x|Hj%lMs;*(xCtO!<8&8Cr|;Gar7gCtZBmiq@O2+tl{#&kShVaZWi{rk)Uga@;<2 z)a{4Kd~ldx_!9!VqPTVX&2KKfmH>RBn>y0_Ajsg#U?$ zT$UV0+~6~Of?mt2lf)AGf5zzh(Q@90PoERUqaP&*WL(X!JiX7ypH=3XvQ$FU81%)wgwq{+*w?vJWEJy6w|U zCW4IJ8lN#~Hrf*{OT{Wn;TVA2*nQBJ z>3yzyQ)F9w!lC61)@T#rAdOUd@MI?A@^P({=z$325LB$^2LBWdXVaegr?XUE8>WS> z@Ys;s_v$@<&T6+DuNJ8{xY!jMHPz=Idubn^9nPB1cLaZH_VjRG^S>!1H(IPq-%a@{ z&1qv&E5m)26}cD-U2NPPh-KBtg9|RFEeK%-om``a(IgtWB=;xt$d=a5U!SNj4_!ME zC>2LKg@usD{0*PK&lO}?r^$=Wy5-)PCXc1M<&0SCk@r3CwTg0HE)O8ajJd5|1NC8|f|`U9Oz%&Rem70%H6;c5FK7-4+FX1!w6 z9Be+A*$hnd#aC(4!Y$FgFOuBPzR(mJ)K#E9>RYX&sWbJ|R;1XuD%q!#%?2n4c^-Gz z5r0K-u3}=s`PoU4(C*LAu}sP-1!bl}BF#S_QSh>sbG;d_6T3qFno^OI^-Aly*5yp^ z`_uK#8av{2)Si24g5alGcy4ZPY~&|DQyqGj5{>VJXT(s6x)<;b7i!2F!FHtU&JMma zGBeB43~J=7#6vuf;RhSva}66@5I_9!6?$SA%_pm@xSf8fGA5E5Jom+;649w%JKY^j zthO0bNaeH3d9^k3;^OSE?_ODBHbZm40sZbVoIVo1Io+_d*|3$cR@yKy@hb}^w9rxX zH?@3|YW0Oh+~rlcig!nKagR%C^<=9h@quYqLFIfD*oJhrq3Wv5f^>*ddU@l5Yh4%RMbc$k6hia1(`>`OHEVg>Pve!_}w zMnD(hs$FKp@%^Kn#*2{UD(ex22+;!_-VLwur-6kh;>BQ*T<$n27=~3QDii`L0N`9!Vclcf2#UAENO^WUa%6kni z8|pDO`Yf3rgIv-7<^>@ffhg#`%3yY*r@36nfOSgGy{`&sG_eHtgNV2bc7`@B(;VLr zw)kGT_Fh)pgf_yJX_07+zI^mWTL8|BrFiY$fplRll0C5tWMo!pykqseKkVnvpS{9c zjqjQ<(L3DxRMLgy!8XEOUGNB`coEcFJC7W#63!dmF@Rf+yw{5V_4GZ3;>`%5RTjof z0y(8l{Xy}twXrg73A{(mi!q9EwGw_}r`wXe;nZRqR4qS;O3&a3i6Gj&AkqnaGdnxn zr6H1AoPRGGIcD^0h~O~lye#jdxohzomDe!|QlFPL9q92+Q)D97Co1!wTp8S+YaiL; z$$PIBRnqa0pZoO=0_Uxt&PV%cJwyIin+Ckn;e{th;bKt`Gw}yYk$&v-6(LcdkNmtP zF&~IVa+|>k+`!(8e?q5WHPU@}m(P|Z(a60pMvos6{4xHXwo0%w>`_B`4w|kCoGyVx zyfriin!nNg@&hrtuHJSfM;ugNs`2u^lcPyTo$qdHZ}$9jmz0zUaKuA#v)g~tn=zNu zQzT2TPkuDRpJ-8tXDckG9`!T1d)#z5qY)F-Vyf-d2%{^yhTbtO&Llx$Vw>5U=JF!v=d*fK?9h&wNdKv1Yp=SJvoW}RdnhsJz z+`y5mc-nq-|CO0&;(f#SwX{Vc)$gQx@`nyIAVbLZPnLhK3^bf0C^$v)Z{b`f{sW6W zu_JFNf=DWol9;;nWQ#kc-VkZ@5D43Df6`M~59+pHG+X5|zk%%VwCGQITUvL1yf*Sc z22Lm;7i}(`N1zMErfA;Ehnch!U=opM5PS#AGvcnse(eY(jbWidsMwiiaOrfF2iWez zZhd}Py)+QHh zMYRR-&^&=OHtqGiALS85Hol;hd3L-Xo2Q(rfJ7{f ztli%kaK&B$33(~~)r^`%=$MZL&5p`pv*$@K>S6>pfzq*aV|z2WY81qG*x}2li_GZu zU202kKjgaaudZ^)3gl2Zm)5VctxQx}J~xd-kWqL*lK;jP$Jb|x$l;+MS~d+)@H*X0 z&CSAg8v$CkBS=Mdlf>+!dd8pb*jiYv?WO&QL`DI?^P1^Wgqx~&-%x1>ryWOhcmg}@dMVX`% zvr-aGiGI!FnpELerD@*hNCbd5Y{;tlj>9h`W;SYiRnOk$9TOR|ri}W^A(iggL&7ct z)EXh)nhdfUcQDHl=cv8?RIQr)Ge`T8%6e=MXs$q09PPqk#^bRt-NmGiG1bMyu4%DD4p-g>uWkT*S~hY zK#Pl}mwoTfW6?j(tTKiFV7_T@tLZQ!C82wkStae+7;fPSWQoVw*3I%V4Wi>?VkAI= z$SR_vK7X|P{$6RZ)^Xt=S}2H~?>Y(`eye+F$;%NLE}b;mWswU|9~^+?_yVKnpRlac zEjvSQk7yzR42^%Dy$SBm_;Cpao3YZ8(KjaTM4LBsJ2l-w?7VIibrP@{6pT zd>9Sarlc0Hm49MEwnl%P-V$v#l%ux~gJdQALI9jVi@9U8ttAkR{ zg4Yh#;WVfsC|*rDD#(@i-H@M!?S@aLN4!g4Z;oJfv*l{rQAkqgX3A^pt#@78y z9q;vXqd`Sv*yEZC-pdd7!hlE7Ug=%8JUaPi{b^PgTD+VKlgZbgApG;uK{wHh&!m15 zrAF$sDbuKk2yReqmYVHdzTXam_{;OdvT85>xkPLWNc(h2*yYM)E2HUFuCJepEoY?8 zJ}$-Ltr?_EV;TL1v!^d{kb2PROoA@{&WM@xv-QmyRe|k ztU;rJZT5paoh37wyLLk5@B4N^b^dvcNJi>1#^M;!jN-^J+p&tLf7gTI2^D&8zME%T z(BCUsj-~=EUi5|@-`~9CgV|3nj(k`Ky>shw|DfSWC4k$r!m#hn-yIU=;|AXOb03GE z?(#6u_`(Un&HK@2g6r?V2)QD`f;B>({>#QN5aw~Z|%H*Q^cNjtJVCR}O zB;5GJilD~){}nVfIy9?Lh2YNYZ-lZNEF_`kI-!(L=cNAzRfY)O8us?<*&LlfZ$7S0 zW8CFRwGx1_N(!}k-id=A_Urgx{+#9+n0=N<>+UXVX2#cYy31jUKLPRdeLLpKwWN_q zv-bDq&&dR#KkN5&r&}6gj%vK|_w{$d#D}oI&&sMn&X#%_{)QySAH0DwP6L70YpL)7 zk@t6~tYAkdvCe4Dq#=?O>Kpn0KFJ#F9*uV18?OLdL+7S`bh(Ls@PPd?C_eN`xYzPn zXIK93lf1xc9ZPd>9KMC+Q3_JaUA`d;kU@pvTm|H+5P!PC<-c5sE3-IqZ{Yrql(<13 zVV}rL_Ka{FWP%m+6f!NAOpCoM@BWr%&n3an-z2DwsYAk7Z(fzWeEyFhu$gW#5pib2 zQpWFv1z*NA3l-BZ5rFu5))3+QCv@Uhi2(8S5rp~ehS1f^>_esmcGcuSKKRYr9f98= z5NE*ooop8QZ+KhMqI04+N1M5pD_33XBzspuXx=37UuJw#Oq#TbiIZMGMuPkH?TYeG zGK~#>4h~XlADIRPs6zy0E;+?#7~$m0vS@-?&#z9Ce}y&2EAxwsvu1v!FtNS;&!_5O zi;LVyyL$r{(zW8}LyMt+boy%fcmKc`p9J9@O)|~&+hSZjt&l`M^a$em2NL_T5dDbQ zU^y&JwrgkN!+yq2q63Q?Z2hwP>7NB6Q})pv4BI>xC&x$PJ@CdiH*WVq?d@qEf^GL?S@O^CGr;Cq2o!&UhT4qF{+H7c0IVYl-{~^6?sWGv zU!%bxew1~`OF$QOIBBpjKTP{y;KyQoXw0R@coZSloB!_qymYbx8iEY>x$Kj?7nTk3 zH1yb*Np8K^&9-J=F){9X9~tDrUObQSQT`c==%JOsV2C2bK=(Y#&*5maI>N?$c+QeJ zN6r=|@EJ1+sl@Xf5`oDbEG|xco*a;5$HCn|2-nSwgGMM2exMacE)vk96Uvc+Gv50J z(RK5mci%jIlW4{Ver9nfrVqtnWhD3#=&kU*BQ-EkTzmoG)ePD;xhr(yNIYy2YDJ7> zahfdr3nlSBJ|P-{FGufSLchq3VkW?;OrE~56~)PkLw!1{9gzbS6$W~^Sg)n)8*KU; zU`z7QY{S@b%rJa?Ag4U&9N&NW{C@sQmV>Lj1=xfZE*F*i3{mR8r&p!i=;r!odSrBA z4{5;G^=aKLXNbbV#5msOyuM-Vea6@sN;-&nY-@@dXU|(%3_dUn3Oca9tn{c^Jl${u z5H3eC@jhZy-$6c}O-p^$c4nO5_qg(D&jz{r!D0XB>IMWh824*O4?w}>D5vtNm+0~K z^!2^-$#eBN+v%pRaM{okoC_dQKh2S_r$$Zu>d-XIItWGh4(sB@W46hl0wbk_0xhup z5$zWE0^mit&S8ilk?d|q1Zw|Tu7rUKgxHQZ8jVbJv0ym z+@tv-Ji5Yikcw3;I{=STNYnjbJ=u?0sMZ`6ng1SP3xXr$a^_N&|4ntWcXIJyF^I^# z4kj!yy8N%z3iV2KW5t2tyxpjaHT`J7ah3VdBk6c_uR65nvp+K*%rbu4x+r2+AYn^ zbAPPJ+moTV{s*8H;$IF3%G&i8sJ`{< zb(z274vI&0z-m0QAYNMr%n(uU!3L2N*jA76#?gjq9E{2MY?JoZ#-PWCTbxCYOO6A5 zOtcTsuTcIx9G}a>5Z$15dz@MUn`nrV8XComi?wGEQR!q=9wgVY!# zSUm;6U}|=ifG{EYc?OBGSxQe1zB|05Jx0Gl61v+Pe{Y0S6JKQ8XUb_v*}A5o`YN&M zaWHd+oOb|vcnjSU4-~;5`+^wnG`&*^LpP>{5A%YO=1M;vcZZr9L~_avPt*zX!xdK$ zIkttmXJ6^!(L419QKbjq^a^f@dn1<;&4==oUu=K67Fg|Mg!?0ya;4$y3&9+y>R4G?? z^D}1cBx*uv(ZkW$`~B7)B3;n7g^{W5CS%=7Zabh%c#ZLoU_YgUvpM0Bmk zN;w9_E*g8?O|IW*cuy;~yF8c(h;^0CmJ6@_N8!9Ltc3lT3=QLDkO8jeC$kp`x9>eJ z81;P#M2O+9T0{9z?V%O|wX}z-PUhy^c&5NFoZ_hJ_Ly{?MUbZ+=5y==4?EI-i%X$-r}eBw8vxLgsdLFfMsr zE9F`WAT>G03|=Y=Nw6K0Sy6b)FI6TH$0l|7UHgO$qxY%Kf?HG???aEN8l zguP_L?!3*TD=)+nE_+aNQ?MRr9>b|FB=-#p@&f2R`k{9rr$7c^CgU~>((7{ra@G5H zMF|}Gd3htWS#Tdev4|`4ccHJu%8syb6iK;CB1~$6aZ|&?*sSh&_e>UXXHJycnx`Ak zKO2tPX1oJu9@|=dn*&?jF|{N7fc?Y&7{&Ncxh{mWHN2@5f(p!Gvg&^<)*B1nPZt;@ zB9Bl7idkOX^dRJ9{g;GNd!SfEVH*xQiw-|7PN!~6g3tV#Yu^BEAO)e}0IM|_CRMV# zSyXu#gHvklH?H&R@q@)WkHh?8vZKki?MfCZUNG|ikZU_}1=sUQM0Bb&nnpOh_3aaW zUY8Qdfam@FzrMa3Q4?!9MTEM?-r`PAdehxdMsKKoG#AO6p`#eO5ck3BU?Ea00VP85 zi0(KjibtP?P3cWOym7ZUQhM|bC&b5Ro-|k0ZeC&T=-zu=O#ok#^VIIPnzCdbe__o$ z#&f$yh=#6oK{pg^xIsW=3;RSC1h^Mf8xbA^*Gp=#(`enUpKsOP$*^|m7zc&x>kI|g zNXE0MNl?;wgEP2EpGA?riSvU)72dzqa&d0$|G3eI?#kU1Wdlfr7(a15%tVc546FVV zNvTa;hIUyCOQ!p&j&LQ8c+mE-jG1Vp@TNd@U4h%~&(#~!0#XoRVSQyR(@9cczpaL? z=6;NyLxExfl0F~I&{OL^6j74v)1(kFDXA`J&$sBje1^jkAofl1t~ystlxUa7W+lt# z*h?=!`JYRhmS4XoO>|aAG*O$nt9SAo)t}yF~Nxt$q5dXq;C*aG1h&lL(D}Yl z7^1K{vYIDVoSh`rYA8=0NOEQylhv#KN>@)pI+y}zK-PjtMNYU6dp zOf(2&H>mkT*$9e4fo@Cj@~^nbP?AH;`;FTY)$+JDLgj*hv@+y&oR_L*y=jxQqj>xH z%D3RH*Cs@XKaXBQAGBOu8oF0UF!Bzz>P)hv5lL+mi-rv{9<{aqEs(}$c5`A?)VtZ{ zSaK@|gzmkhwemVLc3XBSu>Vw+n$D%+lS z4N*#6YL&%cMhRc%(_Ip;w-wh%$L2}zHS!UW!Pxz|nx938m2JF(xigYxyT?JB2D1M- zZ$ZpH%Cbpb-QSH=Y2=dz`p2`8bCC=0@7MnD^V7Iu+k?VH^lNMI&#J(0JILIp(4cX^O#B6Yb46vQ+ygtuWC0 z90nc&+N%@@$o&Hy$n805&MX2tvxNwOdF*&k$pj*@pI{G~^I1}R;q4sIv75K*W&k0x z&K(DXyH7{YkN@RdB1_sh!TWizoZpOk^GU5~izLBD;`+UjK*7c0=L4la-@yo3d15By zxQO4$GyDH?V#6y`rUI|^+n*0fa>#UBVWVLSx`6pH@6 z$3Zm~0?aJsa36F!UEK~Tj1|VHMCO=S`k2(znPtR&iq$eD{0eKP`#AXLR3{bG$B3Q> zPiVlXeA5ZvMuAk3E{_~u=$-_Xgfs!RD%vIAM5u1*fc;vd==lvi!}!-TY$8u{*bpT>S*l98|`aQ}{sZvucJ&8Lt{< zKT#nK^gGTGEl`-m%u3)ZYwHlW+OFpiatiHm9>=4*qvO7_KV74ai(yoJ^CAw!zCQdi$`k~xUG!#SG@@1){W=4J1W9~Jn+1{_Oh%RymV)!!P>rqw`#DMTt zfz8kUWO^VHB-)fUjcRybGYm!)j$Mg^ih30*V=(;iL#t4qm_T|Di1N44jrrB1!4(f_ z(dMMe6I`AckVP3r=D$xBwZ(Y5^HvoGQ< zn^Ua6WqTNao7w>n*U--B%ncsC$!2s7PgSY6}6q|IsyqhbR5%?7PlX)zAahTI+P}UQttr^48=40zhkIJZD>a=!` ze6|}Xcjk46i%FJvf@W0Cc%qlO2!%7A$GVTgFi<2=2kMi%OdhO!dF!t?s9W!3CAi&s z#T@1K=_0daL8kFLWTY6$7HZ^292)_94(8;{<-)0!0TK z1)N!`();4P!tOg?pJ;hQ-$^V^OlQ1{7nmp|LV@3$k>X29Q0xG4l%n&MFOPG&JolO(~o<`*Lnm+ z4WR>@f|hBpdRgN0yIzX=s#2%+ff_4Wu&W~rD{kO(qB5RkUl3EgOsuhLvV|OpL&KX+ zqv&&iaG9}cq0adHfbHmq<~x}iFI>2uz$egi7!p+>O^&!)94)yV1zji8H}k)t2Hich zYmmP{Bl=b9gcYE*sC7nMvkA4Cv9C3!)|Gf0?y)iS35%GmWaL(#upJOD^&gk$<#ybT zfeySLqPMJ}U-KT*fHYO&Dl4NN`{ws!=d>W+Cma0x7RB@t0upCC?|v{`v(0YDy`@Tc zvC+-Ly+NhY{V2~-UfSaPyvLY+Vt-hr>%b`m<6@ zDyG>mA;#PDYo`exvs8~z1D}(I?6?$zWJ8=K1iVxuZj~H*Roh|sWj@sqx##NbfU0O2 zYzZZtqLq9j2Z}B8gY}6Q=g8$PqV^PC8%>uhWs#J^F2&17ZF*l<1h8#RltR82!7Q}i zv$>Iv73HZyHas-7t)14s%iamU=Onl?iq9k6YnkLR_;lS%F}1z>*eiK<<2f6p{nBnb zzL@56_hX>vDeKz-{^rH=?4}@FAZoHbF8&pK!-za|*8&AEP}$GGc|_~vl$?C!`n`fH z$aG$t1a~V5*e7CgqdcDzUCZz8*ST`y(<1z%_y!9;G{KjBj&Eq?5e@ANDw3!|DEA6V zhkuQ}A~0dN{}n_=te=22^XaB&VEGA$-A(GX6LYVc_ClxH_(1^pjD(z58D330ClX4> z0Y8Q+s;H@m1UFJWyUqu3RH5s8#Op9)U`{zGxJ-fEA2W_;pz8~+v>FuN5?T3PQzmja z^%CVCD`I_RU|eHw=mr2D?)e!|i{FoR&A@g)b6XBj?3-XUHsO*Ds^=;c3d}DNl|@hr zJkL5s^l`3Mm)8!&3<6J<&SvexO;oCMzggOz+7@N?TB*ii)<|>#kQJI6I3A8dH```Z zh~{72%S>>kJLno>DqLsg$vhA(yvJL=zd;|?b-O;zX)Q1rP!;`6%Umukxu54uJzM&J zAEX*YozseXp>a#IwV2URNFf3U{k=fMFK#{n7bHpJ6dK009?1z{2Z-ta?$Ba5(*3<| zo9}+(=M%XvE^0FJf117Q&0C1c~0yKmCcc+gJZpkcMH&m(LZ&!*&ko}x_d8c9a+t2OtX5*i)gwqrP^^sET zw2lNka%oyL_D5o=K-$;bY}{30jD$0oc(QP(1D-3Utoc|W*a+AWmQZ{ZaQCTyFd|M? zH+w|;4&`679O=3sZ~I=LM$gy)SJ(th1IjqIf12raEH4v&S1StHF?JvLNEs`GZUKE= zhBlKxMWg{B6Sg8jK$5JCm1X|O#nVyn;S<+E$^4e-&Iwzd6dgCxBm8MhLMM6BdznXF ze}3G|N^wxG(Psye>pO}6+hJj9!lT~HP%KW8D^FTp?P2Spm>LL$&Fdyz?bZ~o2yB0I zLn9du>X$<2M0%MpRl4M(3V&3I(uCmx6m_gQp=f9^)@zY2OL|U#ramd&<_bRtu1OL& zbKVpvVpwrW?`vWN_uGKjXnNX;tPbT5im6UNZ@*#fPK6+ANoyS&!2V?sH@N5fiY>F; z9@!^I*hysk4mNq1h&oU;kz9ARf4%rtLTwQeH^_CE(LSym3uSIgEVjBlP=LZKQlcAu zW)ebS4xk2xZ~IMGY^qTUz&dFG6DB?I?tUa}y-6t~w>DZbGK@rlH56Kz#9owwynOi* zt0LG#DNR64v=T7AC=Xe656%#BGzDrKB=PtqVzI~ijLD4H1P67=0f%nqScyJA;};!L zy|)rr@zJkgGp}Ynt(&YhLxvm);t#G)L`We!hK2ZUb&{HiL?e}$GUA|?P5G$vk}gel zUi44Gdh}lt7E_bbvi}S?GU1&iFfa{`A1r`6d>klZI&?FyA^NSzM%7T9kBJ=<<`&A6 zsrwbA$Aao~fhjVL1CD0U4Z7aD}pU)c!Sy3e;Ftym5jRJp65CO@0pbzAzybHmVDzkKcZ^ zZp#ULN*f-k|I4}CeiE9o*?==g*iTSl2@;S_~j}&Pq5;7_f!>GGC zHm2${l${$mJPy{Q9h#9c)A(d69Y#8jKHheaYwOA}!)o|mey>?6ncKb{dt@JEm4dtG z&pz~w!t^k#UmMs;o$r{%LATNiP)pw=OI4ckJ5`2`Hh)!zv`=~5G|h)yWzM|W5R`}o zDxze_(Q7+_ioEY7l_H={^2|oPZ9f|yYWF)ONv>u_|9zV}gz{#5SYsF>x!Io6y2j)5 z-oyXtE$<1C{UPR^SK(+qF~wXpugjTfqnO=d=mlf$)yZssMP?o9XQy{%^K95-I$c#` zK+j+BxwDV+gvYS#YrDCO^P!?basJEC`JQ4vJVqkE&a*nZ8y|mswyBi`T=}pfw@wTZ zjpY`-q&b8&a|S)dC>EjXiZ2*{vp0X`odG8#e?feUwb<^K53%x0-Djex>n-as#UWZF zePlWzOlSj9^krD{3Ihjw@QDnyihFUepIt9ZnasNzd0$t@+!Alerm3`(0AsZLITSz@ zW`rkX%I*H}+Yp@+zdI`d-RV$Kbnm@9rfv^YeldP+(cGa3`m%>84_&GJ&2MpxLqkXKGk!M@5s#U1Rlu&2m}sT@jA-8VGI%6Uv*iO zT5}(o5h3BIw*@`|pA{e~<{heb%)f($95va9nMg9wr${3EgX}HnL1eC@gw-!{_t1Np zmv~C94EzY(NhI(W4txu&203xyp}DSZyHemodYQf15WtNfNT{y-1TYAGVnzx8!Z&I5 z!G#vro4>PWZYGz%xeA_>D`|Qp_1>DP&vK?~KL#N7{PX_O*)JMRN<|NWg@14!=P|GK za1_89=6CtVHn1$imOfvj{#AdU&AZPjiG!f{@0+Z)i9I*~jUbBv={boI zZZ@CTBLR7Vq8^+Ky6d-5>_9LPv%<;2M(%Gi#G~YJB|o#5H9w86T&I7hvdxKhX~@li zb||>eowzq&l;6;8xWs|_>BG`(w#=P{WCU4wdF$glD!?;g1gWu|$PT!b3!o@sZwT7= zyCuQ1q3&v+ghqEPOX^O)f`0PT>VT3u>B*^EW?Hy}XQN8i&x%8yXdpgoS7 zsqAJKAZ1+7a0YaAQ8yHT6clB`YSdWkPSxDqpFbxye82rAedbQ~gzF?j{8>qeqgGja&(SW>rKr(*49c=Ei*g>)LBS~VwXS@4V?Z%TkbXrkf3uYJ_F^ut}nSS1cc)JM5YVlQCugaQh9> zmw0G<>asv%lIwRVBC(B#a1m6|0GcNZyv~u9sMaP5RR0Qu-*!BU@9HC=BCR5(nrWBR zDNxi|Pf}`3uq3?!>oaz7izlxWjHkGx+G*w6i_g4c1%=PeR2Ify5_cY$N3Jtu_$sR< z!033cCrbqQRT@YLz@wSS}gaS=m7F=iVb0ImIDXN z;_tzF&Xy@1ZrN-3unR-~sQ|hma*xeUNQVaIbv0-oEP zMWUfu5Ts&onDN=}1sB?yanZ&Z!**WdQ3$A(Ob5UuBo_LBpHwq}$eU~Wrq%1kkD$B| zhtMX&t>?X>p=dM9DdK?S6jJ{eQ|Ehf;w9vX$ zkNTI+H@6m7WPlt~2GS~hK{qK!9e_VYZ7Sz~2m->Tp}v3P4;*6r8>p=S2aep4hrAV` zpHum@g=Za43HcZv-7n7|(p58KN41rpoB*Hf4>LqPuIG)H>$0BzVlY!L_d*o}>Um0J z)up?@XreK+3hWsLgx`TXMyrIUbKun!`Ow1n>&P-r{f|l|IMv*lI`^zcgjWmR4HKVB z;)~`4xB|9g6(&4IG#{cY6J=h^ya8yoP>qn8Ii z#1*+GlmVPtcW`ZA&w(5Ayk{9y)5AY0iw~tf2|;aV9$no7c~hIt(wsCpor1?AEX8HE z;Z>Q?GEpil$}u3o#IjNAc@@#9xydTkehD6{O9|Lxs>+cI9^+USfSxU|u9aEWJ ze(@K8V0>Sll>nPRc`OQNbLlpf_tE-bO0^tGcfSJ(>V+bQYS3#G3|d;ZsjU;|Izw~g zm#U0vg4;jtT;gxvwcdC3o+sGv9Hjw3|8GmM+GA;QDAC`1yn3(N?V1BdTZE&T7;r&| zGkTAgL`bgU3;m+eroXZ#U;c)OOwkrQgfr?fj1sGxFw12%F9RBXM%6G6Z7RNK;i zA@h?n6E1Hrua>#ULP7;(4lM7SnT`SqH$&pd;=dMCP{Q?KC4gSS?~s;NYQ#jX<9Q8m zUZ@<3D;GR9FH$B}Md6iz%v(ezz(4y1H_R8f@vt#Y;LII7cp5d2e6D}zQNFF(n=fZ- z#eT)i$E;^|jj@fd_D&fm*xAib7L@^Zm5|F4hbYI#6L9k~nGty>XTgB&5{Zt(6-Ciw ztQ2W1p#xqdM+17D;rS87W_PPTRFT>|qRlE#61=q*R++^u?bY&K6pGM1;L(1OeD=`t zSsn*r(M2U=Dk7P06-EG%raWdG%))YcEjZW)Q`zr!mkv!PQ^{;D>}MpMSu+haaTM3N zrduf4w_rbGk@2mWI~622QQ{A=S&;p`A)QKuanG`N-`yT{)UB(t=#WI78jFcbu+u(x z_v((c5cZQ2&Wag!RIL$J2qtxF%dI!x>)MIv2301qiXk91a_@u{@70qoVZ5e}I{?Zpr-WAY?>hYHvCncsRXY_99R{C2U(c+}OI{5Btdf%FS1W{gHg?tPRB9I95@QB>#xD0DIQUWEc7;Bj?EeP5 zUzfg#*hWt4r0e({f5?L!i(YqaQA!+a3}0h(7;=YRp>6Pzgzj^feEafNQZ$rVXQV() z67=`PTa6pkB&HeCp9tUToZg&)zjpL?7r68S6eOUYsg$n6lIeALb&Hh2H>>MqlwDV4 zWg>f28Y8YqVD$HTe>1K3t94#XnrRPA-za553~^9Gq9MN<=;c%}pv|nkruYJ5aoy95 zYj-8x@R{{0cIIsOa4=&5zKsRsj0&sKn8_02y!5iwn!A{yY8Nu2*QlwNKk;Xnt9Ml6|ul`Pd z7Si_2;BB9LjaL%uAEIFF79=q8IV>5hq^HicbmSE1_8^pr12k6;=vn6=HFAA>e0WQi zg7zB4b05=l(3?UMGy^E}b<{wr7I%T7l}Mx_@b-(W3EK{EbjAZj%|`h&caQ8*_;MUy z@2Ahxz4?r(kAVa)YGzpY^&j~E<#zx;-T0gS{x<+%hxjK9y4D6Qfv&Ci(EIgro^F7j z^TXQGdFT8IixmFjlYe~HIj9d}($hHro9IWV38U0_zJsVU>Ab!&SzWZaxL8oXV-B$1 zw|pq|M6+i@6|h~ID*yn;3Y?ntG#xDZgC-chp3k4-!QTo1ZVij7QUTS_ z3V07g_5mHA@LQOz{wvJB*JO=HRxJ`ud`6Md)kVNJiU39sRHN_`0Q1_qKltn>u}zd* zrhyA&(^cfE+f&sAK{(&S1Ynen9+E>-b1Hxd_=M_zW0fPe1z`aTeNHAgtL>AZCkYsP za=@Z|Yw`9*9=2R?0Cqoe7?5_sceztv{_KxN^|sHruW_KL|8+nagZA$%?o7jaAUN-@ z^-;c>q6J3C@A(%f=bqqFoJJfmmG|k+i$0E;0vi-C-i+)w1$CS`Fsvx3fE{PZSPv-N zvJfn8cK{_7fq=XSB;Z6)UMdtPngl5uQw2qXvwTVVq*9BOvmLL7n!_xfSs#`SajX}V(!Lov#TAR8d+ zumT;Rx%Zg_7ZEOZYVAynfZRF=$Wk9?i>9u!8N1pC$ZiJ_l(@K^0?0}I>B8EgyR7?7qn*;G!GjI;zUM6f_z*~uO2KiaN zl6&@a?2&>pC)oU1bpkywlW=C2YUWt2-h5B|RfX_>z|QOY%+tqD^W>0tFSi843}uff zAZb7|D$Ezl8wxlkB9`&MI!$ArIl3MesBdC!TAP=0q*2}wl@x@NF zMqkq{1cfnkzlH0V^ioJ*TLlDkk($X~Hf|qtSA<7R0F#|k$w-HuBC#SHZ3LhmT2LAt zXr$Dj$&4~6R#uJ!hRM^)A3_F^&Tk8B%pssdkOfFO>bi!+29XC(lF^#&dzce2h+WcC zm308mH#?>V*_Gzr>4&aS47PFSfYTWLpBvER*AkJ28~hE8hJUpQ`vt?3nC)cZ(4lgZ z_b|FE!7H06d5jAy^0RQ%eWg&zW12*Qhi1~{IM*bQK*T3mci951_<;PCR<@xhx4I2y z=OcP*$e7nqPKKi^GsOwM>jDUI%2wka-cGt=(!Oprai1;(y|T19QOdST5*98~wT^&- zTye4GP7(8NCLBQ7yv-#Yz2QHr$Bc^3Ar3Q*5@jF(ou#=rJQ*{uhcRuM$jUOD=$Two zgKPvA|ED_gHh$393n^{)SUj(r?TA3q;u>{W@DgB)V*kr`j2OK2d=pLts(j^7dry;u z$3mtV=6G01@;zqtQEMXtB*!zU7V_(zwC7O(s2oSZhs$ z@6utKLPZ$j=x`g>0tk65u`BSfW}x_^94)^+1Np%xfYb0@v2TLD zA=Xutd$-?gRV>*ly4`1bDwye78N%BXxupzjvDMe?K>ixawVkL?NWpCwu$sN+vgVvBAVTHcJ#f%=eQC&R_&-nE9R<{Po~%le+Q6e8Jd&{h^4qFEsfMS37ZzFU zksrRMlB~&sBrJisd^bwEQ?EXI;4YqiU@8NQsdK)HS~+I0BWb77$8fQm;$;}k_rT06 zW@5B&#%UvrbDqnIdhNW>=Q1}2?gK{m9W0C>Tei(LzV@K$&01=gPRSI|TzSeHd;}Q~ z=;RF5!MDD>KH5Fl%sBemaQS1?N^=Gx8ITdq$(=HI_bcuJIgVcIKXi#sfRCXaQ>!P>dQgR%%u!hP_CDTMv|Z=6RcvGQ3oB6{zw-nk2tGo7YzxiC>697+P| zJiWId4RA4R4Iy}Vqt#DSc7T%>*Ucr;6+T! zQ?=!1owr*25OogMx?3b-5rzCm&bcx*%|?53g(WB)<0^N?^G&sP+3?kL?f`kmp1aAX zD&YuYUvUWtP=3kkz!&Ru>h0nnJL>%5Vc836&)0Eh5HU)Sa>xzpxFida#VE<^Gp&Sq zD*WU$&!^SQ+R(qC9Rq~39OXX&ChgjiljKdOZ3A`gLz+q zeUWAV{^EL5ktAy-Tx$N;oni-ng094ila>n&8@>|BBF9F&n{5$^(Bd1?T|+`!1P;$W z6b}e%hsMOR2WO@{P-s@;Z z(VCYtqamyK4knDx2Jg|7>UedjKSY0Vz$YQWnkgVaE<9PT$ah}Dz4pZ>?P*Zq-q9=R z?mlKCu_N#;LPdp6#A*6L3;HoV%ro@iyM$_O?3sDk)I6DV$?TLf=MHQs)dDxOC(4+r zdsC)gk`r|hdF!OzqOr70n=iYb3(H0y#|tQQwk3QIz6)oPA)qGz2@hejQJzw=1n801 z`c(eVHJm!aQeu>dOr1bWC0vezgyf-+KO zg$wWy1X@V4I8rE{*7@!%!{&AF+A6hu8qu$@*Se;)kUlOjibWo=ozi-XUZ&uNY;ZEv z)~xNT%2PHboJ??~Y)H1`PXl>{jOGf1Vny1U)-^iR_b+@>F^>fWAH^-~5|~~|yG$0U ziG?!36MVmnp!Gu%4UjH%m8Y+?2H+446N}7D5q6sVlh2#;fIp>kEsYQ!P2tSyh4U2j zWYPF4(G$2M_r5(%N|1G_0-Zh93%4q=1aPbVPiJ2pRn^vhEunxxsz^zPfFdO&2Lu6W z32Bfwdd}W! z?X&k<^O?__b3>Ys*=0Llri9?$9D{U^^pT}B?yG@wb|qs%8`G6EY;G9?TSM{cxX7~& zmjqw@g?j9v0i^|p&(Pt>u;k!2egjTS+YB|^SY*S`{u@7qRln4)k^8x6e>~#j!^=y zH*%Szk+0GG5NIbYnBv2o)u=HEmRfXeTkGdNszHs1!4+}%_-jMoSwUtwvxm&!XY>>1 zMw^tLr;94ccltE|YC)3F+>vMKjLBY$YFrt54cZuW=FtzIM_)>c5V z3#n4F7EtTa9Y29TQCaTWd7<-(y;Ga92CB!3x;)*CJCc#b(_aEUHL;@5!6vBu{n?>C zkb zBviNLP`p0gB8Q9oMiHiLCG<9DsK$-Ppx(RcV@%2`h=LIq5Gp5^s9o^r$n&wiTWIkr z#PN{TUEiCR+iG{e$q}69{YrU0{o$4Ru}ek^`8$VJ8N3Tp?2~SiGZh?iLP{+4hCeB> zq9VuCa9#db>I95Q6PmzV84j+f_6rF;PNv`Dcg9y(2TbOIu7brdsldAgOLo8x0%61j zqzZ~AC#wG;#<~hZxo|C1Q(8SMe8O51`$G*HId0M>&-9<-r}pU_X7O9m>g?-6{YRfD zAWcq;YLL@zPpntK0Ua$#w}d$37)JJPZmw# z=_b+0GOxH^Zmh4i_sW#kixE4lU-f$^DPKWnr!p!))N%0sNG|ZEMs!jh7w`7M|9FnW zUJ*xf*h|)X?aREXuf&#gD=(<=f#W`g6i5PVY6I#ppnQjH<+W8dY@3^9!C_dtMsAyp z3ul6)na<7{NT+W_)TMWPYwQlz&~y1cx+!?@;Svt8x=Mzmdk(nH93*X^;2@-v2W}`^ zx5z;7s1>P>BzL-VAW~TgUHkD}4x5&e!X3pRmh7`ouMe<&QuQKoh@pN8(nA@fn!qTy zT$lJxJY2GG&pih;-UyA^m~b3^3{Y|AS?mAO%;A5s2xFWth{*226YTQZ)UzF`$pM3_ zP&y}tvQ~tueMHe^V7*GYv4^%`&U)?3@ObX+%kiMID44qd<&L%MP=&v;Z z8XBJ|EIp`-Va*5CNdkl_6>P|WC`lWNOC0dD4~))IY@+7`$(@tGjR_^MtaE3@WWY>tPbE zz%mE7S>@zu+)4}2N|oBW^FAy7%j2$q-&DSwy(MCwHApB5AOrcDRz=U44;0`SxQ;VX z5GVi#Nyg-o1_-cUN0iYN7EiR)l8 z;1_)B%Bzd1D7O^k&?zsk8xF4O;xvCdOSE{(NjvQMT=pUx_R%b466Qk~%RzF66KXcZ zC|IR=Z{H-PH0cPmDy%^is2{)*L??h9T%#7oBKBapM@n;;)-p8zn)O*^E7|mcB)@z$ zj$oQ4vT5k5T?$BTdHD90M~JRmD3x$d1?`P!VE9br3#HcGGmR7I^XsDk<0(v4ml z_I(*_fNTGmU#G+wwT*BLBk`uHxJu?D8sil_fBiH(*bj$p)Fg*3O`rb$ieT8jrL)+V zoEc+c!ybpL$W2p>f>%DY3QO4lHEkjP6w*c>SHSXM zDO+57r9GK(?;ltMb!ccnwE#-@(!uMN!`x%b;LA(s(~wdR0Gp}e^!QJ^LxDUF+&ugG)n)zyR@zvA)KU4+kbIQYF zpTL@V~ucoxg#Y8HWt+;QdQvbo}R^ z)YS---=oyEJ`-1UdSiEj=KIM+9SyCu=81dC30(PVUw{cIdO~i|@>VDGNO|s~Eu;>w z8oay)j8=a2DHW)q4}CwRwzqLw{P?l2vAYrdKR*mbNAR{IA6)x+fHX6 zXi!VF6$kF+%>6_mF-A!z>W~<(w7-bvMx9~W*)#lu-j>~59!cz-yOnS3H@k)#=T!}3 z8s|xMXr))1yP(8Bc+7V;jq-eRfGbAuXN=DpX!tLYKP&Md<@`cn(JXn+k)1j-M6|dq zl`Ua+T2*+Gd(_B0IZn}**0KiduvJe6az42+rtWu5wF;wES07b*M%d0) z4vP+^G!4g;8uxBk6HB5^p9v~l-oqhqFd~10>tJ)>v{SPV^a%AID(*UXg=W0M|8h#ypS_IuG(+zKo0qdZO>B9c*mZhsE|!Lqq(OM|`Q#G$lx)nNSH_!6n)#ms0KLG;D)h%|e3?Af4eOT$}m z^t_%_nD_APfSY=Rq-NtVD%PW?*(tR#p`)abs;WGDTebL!fhRx_} z!lFKxUXj%I0r5T&?I*+LDoOss>)--wHFUcUUFn7@D-LF_ikFqG?*2wg>#}pa?=X`% z1z6`V)iuBMc(^k!`r~1m+O04=4wDE+~ zRk9|+ajtPo(+9X_;{ed&e^mtWJ`YWcho0D3eJq^#c=7B#@G6Eprz3FjXgRXUi_m4! z-BXRf&MGQ-lK32mY$-)-&`j?N^`Gn)blAi?(Rbicf$<>X| zsFlTlbVo_fh%9yGlqOEi@ihbPxK0d*F!jM8d_WBr4&q1H{!X0TJl@xmhKacUOSf5< zujK5z2c|Zw4efdI!BIy(Ll1|AfEy6B2G1h>2x%$@_JZz@GRqrGnhLm;;*F?Baqcc8 z*B0-1(ZA%}8wkyzCOE8cQB%<668j~4GM>#nR2E?vg4r%h?peQV&7h+ks03PphEFc` zs-nzGP(t3w)@QK;&H=Ow$|3 z$lt+gKOmfEC5Cfh%pbl-z+nct8TbQHfy*q?dGgj9@L`74KTAa|JJhxFgUL)=b#v!G zqz`Sj9F=>zPib^UKv5O{HFHSE$Q$}e^LPqEfM&H%{EitifeHbo{JKNa%AKqngRf5@ z3K=Chy#GmCGX$?jc z=oj3qb>EIe>Vkoa)Lv(!&44Rb|4v#=jJaxc=DF9`_s;ttTim}k^-yZX! z07^jkBSB#KPJ7K$Fd86x{$Hh`-^zId8 z&f3X6rxUhqTbb@lk{v8E>~l}h$9q_o!h9ZZaPR0j)Y}=&J4>h4S$d-LMTwXVBdl(d ztWDu&r0_^Bh#X{sNH>Vr0O?wC6SyS2=K+<@UPIhIqQwM7*cK71i3*()XIJx0_blnyTYO3T4=sqwp&vfUQ{2GS%rI zS^kT>HkOmXPP+6+Oz!uc2hfll!hFpDDHs4>N347qmYP0vAMyZ;Ode|^cL&I@OvYN3 zB^jpN7;}Vkd1N{xe8GB+m&*N|dhuIWu)B1LPUde}$M5Z?4t$D97m^`~_3Ik2;pdU# z)_W3m^F*ZG15Ox4Q=|Y7*9P&+!QbwhDH4uyI^8S_KyJXCYtAF9QeER9F6%lx<&CaBhJ3D{xh6F)MX>M4#8j&Q5i* zCj+_bR4_=MfkKi|-0`byC5kji85FU9WsNnP1AD?=dXY23G_wzKH8OZFy{#0i%WI;^ z1ZjNc!8E3P23#a<_qK~8|iha2l{?=FE?;X&>YurAeeynDS?WuPmD8%dU zC_y*?&pw6|iExSAAcMO9b!pv_O4@OYz;!7Ox1MwfZD`d5N%eRtOdFS9sH^$&>(FvvLe~8D{%Kj3>q1xbYL7cz+?RqOg zA9FA^p}<%vTGl(LnM2_PA>4^-Y_&sxF0W2zc%o(^Z+4jD<(g@(Bc?T#Mq6Ri-{G#& z8<>Tj#mV)l>{msG!bzfjQ1~HFItBU%*5A1@ix;2ur|B zWgy&lrBWNL;fZRNv_3H7aZr8THArWj3FWeu zq)`Qrg04rR0@lCp%AGBa53E`|tnp!X*Ni5;4szu~)89icXYWg&P4*A-GLI1AAZmRb(r}H{Vv^+)-T^* zW-AqTD;a!FOG@HI?v|0D*ufH0De606gY$q|=0FEWTlNI$$CO*^WTg6q)$USKVBDgd3G$BAIx|!ARB8nfqTFs|~}y zWp2V)v;VkLelKBqVzP3q=>`j7qM4NHYOUm{ zdqq>tC`=|7`#nr`c2+G#VD#cSY7MA zoS?rh&i?ArHu`Lhe-G@gFFocy{?sDOO*7YNcrLPQ{oo@34?~jzn$fHRbLLxwLXqTc zblORg-jeXwenR(&V3Nfx?{nkRVy`eFLZqj<3@iZf_)c;Rl5Y3C`HX;io9E*&DRUU$ zb*HG~A!=&+5#0m3K=&(PX>S!>bNXH_m&IwYYJ)v`jjH7`k2SUFMr| zYO*=`-)J#gIux+t1s(I*tTTKJmkUF8ox>C6UpHLaA2~PUBSUq%cAm;k*sv?G;n_Gq-)IZw@EcP%Q zYsGm&XOjy6=EOKemASe`^js5Krt2Pc+;$w?lJ3$24mTBf{vCQxiyTWBqX~_}1^Vvi zWC6w7KE*TKyAillEOZ*82i)iknZGx)v1~N={u5j}MwKS4K6#n<^dKG`uLEM~>phu` zy#P0aJE<_%Blx@DFA(XBG~ww7N>{eI=(^vDpufVNXj>q!#9~gghFe6YRokg*KW=io_50eD(pC%kE& z+n%?2dF}oCA0=gG^@m2Z09w{tIIc>K%pb0QViN%{unQ6iMH*t*4%}5x7 z#6|W~@?Y3n{yvH!;v;QL*1F8CV!J{-iWY~7etcs$mH?JGx4_|kzH#9i`2iyr3!5j| z@3gD&;h9}vmk`Ml5MrZF80!O!*E>b7-eKpqk$e;~Cy&F}`rbbYv$oc_A;1*nNe@L4 zQ$5vtVb~%+vW+1l${7|TxFRoSG(Q_#{VMTVDENwZWJD7chddA>BbNPN5-;Sf`1OGG zPkp7-b(bWUdy)7eIQ17df8@j5`abD_&Po;e5b{<2O!IT#hn|KyG1tO%*v!QvO6}Yo zU+>Q?Mu$=L1W>OdueFCNT}~ z8~2x|@##Axe3eU71njB}*M5$HuW#Szh2!kju(Cy7 zXkxzcx4dgZ@XnviwzSXQsQ+Ro>cG^ylN#vQOA=c@md6o24UDV`xE{s=FOG z*Rb3=tl3gfX=w-rqlr74lPU$#u}U$K*q&zC7xPRWySTu=bwWltE2Lmx(?JhJ80HI}<}r}Hq@Wk79Jl!f z)OZIZG2cTTf7@<)^eztBi&6la^(Ok_djU-4Uz|n;i$)>}v{QD(vdZ@_17*(#>aBR_ zap-j-tCAe(ZMEu|>ek0#lr%EahoH=+e%`)&U28@7m9G746CH+j+qc!2P45!ir7JWfrsy zP6S85PX-|gcwNP+XXq3J@C{T-jR`{^#l}z{gh_}nzg6A3cBR)Y^Qxka84{DP775vO zpb!XM69h#~Jcevo8I?D0%(*dsUu*7ql7MLLOcwCAFw6YE2K_XMgfAeNlALF3IHOHc zjz!92j%VS?Mu`bT`KWNv@*A+gi!W}AIVG1_wc;&1=>0M%>}tlFuhzcZ4#@Ew$a+GC zmeu;%TbH?-O74bC2sbRJt zlLdvp4Jh9G9?ydXFGO8Oq9r|(&I%7wC{lMJA&!l-;Bd?w-`nLE@)Y^|w7_U(S9ASv zMw?xc@j}k0_O85&4|tbqPuAY8b2WHNIt#L-x4x+&74PBue;Qd?Snu)!T znxxup8$!-x4e#)o89G;>tJV7!nq1Ux;B7rT1PY7>2mQwhkP&Qm zP$Kh&GA}siU*I(kNQo|&l5qLwah)9Bn~&EH)gBDcZRZYLZ`j(9NrUJCBdf5gTd^nA z0%Rb1<5jJvQx%RhL|mSYHJ~loC`~hT8roSl=nW=~1SDrrWDG>E=ufViUU`0C27{9Q z7{ZF|J&TxcT>RvNk_OC|sb-K}z8qN5)xD(LoY)G5@c(dtjm{7Rz z$%u|!1rnRb?%Kg_&;Y!Si?QFZvz%kz^FsVVH@xx%^Ze~<&lg8YV*;(V?ZWb3$e?*%S;_m^EI_CqY8@fWrRvr%nd15{_v|3*wV#iyZWawYMS@QLus4oDp%lijwY_&K|vBKG*`N6k`W-hd>gu=VqFwlw8&c;ZF(& z4Mw8Qiwq}&uQ1=|W8%JxZq~{g{^|8Gdpq(xJ&NdD2sQ2nEzvDSSCfq(7WKwTHTiGo zd^7RCp)(A|Ahas$?qfQ(7`GkRNgb~+fp#&%Hu~g<0!q!YJ&In`q6GtNc zvBu|6IvJoXt`$P7ZN4>hH}I{x-tqH)r3i0>TsmYJ56I}@ss(P*tu;dbce=C|e4UY* z!yKZj@#)2Q7WOxQ4(EZRot8@O-VR464F+UNIp=#J6|iqpzj0&|9`rW4!BqQvLKk-e zPN(ytJ-*`?+(rV8`@%zs{ZIhp-sD?Q!LzloX>iK4p@oZ8d*FMt54?_${kZPq8T(n5 zl!)N#R%s>=#hF8@yCUI#XNlAGo?(}F{x~ymV{1Fu!jdbKqEBb0VnVw-o*mn z4N!!yW*;vWQj*1`f1c4rV|%0{i>nj%G$w(u?MRNH#(ypV%9e0Suf_Q9mpotNXHPl? z4HySHW3C<>h-8BWpJF8x&m#$DOmrHV$Ev@@}{V>-1s*ni28ZI?Vu%U_m; zV7x*(b}*Iuj&DzeyAT@jd-(f@l*ds)Fe1vXRy2M8MrN4gD$8+7sB5Uu*jQrWQxk@g p`T31&oQq26@WiBJWWGD@t=-~1<4^wx-nF Date: Tue, 19 Mar 2024 09:30:03 +0100 Subject: [PATCH 006/567] possible updates Signed-off-by: aramissennyeydd --- .../README.md | 196 +++++++++++------- 1 file changed, 122 insertions(+), 74 deletions(-) diff --git a/beps/0006-docs-personas-framework-portal/README.md b/beps/0006-docs-personas-framework-portal/README.md index 96f9a5944a..e79d6b11a7 100644 --- a/beps/0006-docs-personas-framework-portal/README.md +++ b/beps/0006-docs-personas-framework-portal/README.md @@ -79,6 +79,41 @@ The benefits of restructuring the documentation according to these ideas include - The Docs section of the microsite will be divided into two top-level sections: Framework and Developer Portal. - The structure of the Table of Contents will align with the outline proposed in https://github.com/backstage/backstage/issues/21946. +### User Persona + +Documentation written for the user persona should be high level, assuming little to no technical knowledge. Concepts like the difference between Backstage Framework and Developer Portal should be explained at a high level to give Users a working knowledge, but should not be flushed out completely until we identify the user as a distinct user persona. Users may also be Administrators, Integrators or Product Managers, but documentation for Users should make no assumptions on that. + +Example explanation of Backstage Framework vs Developer Portal: +"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. Backstage Framework provides the tools you need to build your own developer portal and Backstage Developer Portal is your custom developer portal built using the Backstage Framework." + +### Administrator Persona + +Documentation written for this persona should be DevOps technical, assuming a strong DevOps background with the exception of a Getting Started section. While we should assume an overall technical knowledge, where possible we should link out to existing strong guides for the technologies we use, ex: PostgreSQL, Docker, Kubernetes, etc. The goal with administrator documentation is to give administrators a strong understanding of how to deploy and manage a Backstage Developer Portal, best practices, and possible tripping points. + +Example explanation of Backstage Framework vs Developer Portal: +"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. As an administrator, you will be interacting with Backstage Developer Portal primarily -- this is the running Backstage instance that you're managing. It is also useful to have a high level understanding of the Backstage Framework for mitigating issues or better understanding how to scale your Backstage Developer Portal." + +### Integrator Persona + +Documentation written for this persona should be software technical, assuming a strong software background with the exception of a Getting Started section. While we can assume an overall technical knowledge, where possible we should link out to useful guides for the technologies we use, ex: Node.js, express.js, React, etc. The goal with documentation written for integrators is to give them a strong understanding of where their work fits into their company's Backstage Developer Portal, orient them to get support from the open source community, and prepare them for continuing to deliver value for their Backstage Developer Portal. + +Example explanation of Backstage Framework vs Developer Portal: +"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. As an integrator, most of your time will be spent working to deliver value on top of the Backstage Framework for your company's Backstage Developer Portal. This means creating plugins, theming your Backstage Developer Portal or adding integrations to internal data stores. You should have a strong understanding of the Backstage Framework and have a strong understaing of code that sits in your Backstage Developer Portal." + +### Business Stakeholder + +Documentation written for this persona should be strategic, assuming a strong background in business development and strategy. The goal for business documentation is to give a strong understanding of what Backstage Developer Portal can do for their company, how to deliver value quickly and continuously and guides for pitching or driving Backstage adoption. + +Example explanation of Backstage Framework vs Developer Portal: +"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. As a business stakeholder, your time should be spent with your company's Backstage Developer Portal solely. This means understanding the value proposition of an IDP, what improving the developer experience at your company means and what a successful Backstage Developer Portal looks like. The Backstage Framework is the technical underpinning and will generally be invisible to you." + +## Contributor + +Documentation written for this role should be technical, but should make no assumptions on technical strength. The goal with this documentation is to onboard new contributors to the technical stack and layout of the project, setting expectations for how to write code, documentation or generally contribute to the library. + +Example explanation of Backstage Framework vs Developer Portal: +"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. You will work with both the Backstage Framework and Backstage Developer Portal. Backstage Framework is the technical framework for Backstage Developer Portal. The Backstage Developer Portal is the end user for your plugins, documentation or other contributions. As a contributor, you will be working to influence either the Backstage Framework or plugins and Backstage Developer Portals across the world may use your contributions." + ## Release Plan - Release the BEP by 03/24/2024. @@ -92,6 +127,7 @@ None ## Example Table of Contents - Overview + - "The overview should introduce users to the concept of Backstage, what an IDP is, how to deliver value, why you should care about DevEx, etc." - What is Backstage? - Roadmap - Vision @@ -101,13 +137,16 @@ None - Support and community - Framework - Architecture Overview + - "The arch overview should explain how the framework is structured, where plugins and instances fit in and how to understand the current design of Backstage." - Getting Started - Integrator/Builder Guides - Local Development + - "Prepare users for how to develop locally, debug problems, run tests, etc." - CLI - Linking in local packages - Debugging Backstage - Backstage core framework + - "Internal documentation." - Systems - Frontend - Old @@ -116,8 +155,9 @@ None - Old - New - API Reference - - Tutorials + - "Internal documentation" - Building plugins + - "How to build a plugin, how to integrate it with other plugins, how to deploy and monitor it, and how to iterate on plugin development." - Intro to plugins - Existing plugins - Creating a new plugin @@ -134,48 +174,51 @@ None - Backends and APIs - Testing - Publishing - - Home Page - - Customizing the home page - - Software Catalog - - Extending the model - - External integrations - - Catalog Customization - - API - - Software Templates - - Writing custom actions - - Writing tests for actions - - Writing custom field extensions - - Writing custom step layouts - - Authorizing parameters, steps and actions - - Migrating to react-jsonschema-form@v5 - - Migrating to v1beta3 templates - - Search - - Overview - - Getting Started with search - - Search concepts - - Search architecture - - Search Engines - - How to Guides - - TechDocs - - Customizing TechDocs - - TechDocs add-ons - - Kubernetes - - Customizing the kubernetes plugin - - Authentication - - Proxy - - Permissions - - Overview - - Concepts - - Getting Started - - Writing a permission policy - - Frontend integration - - Defining custom permission rules - - Using permissions in plugins - - Designing for Backstage - - ADRs - - Accessibility - - References + - Core Plugins + - "How to leverage the existing plugins for your new plugin or customization options." + - Home Page + - Customizing the home page + - Software Catalog + - Extending the model + - External integrations + - Catalog Customization + - API + - Software Templates + - Writing custom actions + - Writing tests for actions + - Writing custom field extensions + - Writing custom step layouts + - Authorizing parameters, steps and actions + - Migrating to react-jsonschema-form@v5 + - Migrating to v1beta3 templates + - Search + - Overview + - Getting Started with search + - Search concepts + - Search architecture + - Search Engines + - How to Guides + - TechDocs + - Customizing TechDocs + - TechDocs add-ons + - Kubernetes + - Customizing the kubernetes plugin + - Authentication + - Proxy + - Permissions + - Overview + - Concepts + - Getting Started + - Writing a permission policy + - Frontend integration + - Defining custom permission rules + - Using permissions in plugins + - Designing for Backstage + - ADRs + - Accessibility + - References - Contributor Guides + - "How to get started contributing to OSS." - Contributing to Backstage - Reference - Developer Portal @@ -183,6 +226,7 @@ None - Getting Started - Administrator Guides - Developer Portal + - "How do I deploy, monitor, configure and verify my Backstage Developer Portal?" - Installing and Configuring - Database - Authentication @@ -197,43 +241,47 @@ None - Upgrading - Keeping backstage up to date - Customizing - - Home Page - - Installing and Configuring - - Software Catalog - - Overview - - The life of an Entity - - Catalog Configuration - - System Model - - YAML file format - - Entity Reference - - Well Known annotations - - Well known relations - - Well known statuses - - Creating the catalog graph - - Software Templates - - Overview - - Configuring - - Adding a new template - - Writing a template - - Built in actions - - TechDocs - - Overview - - Getting Started - - Architecture - - Installing and configuring - - Using Cloud Storage for TechDocs generated files - - Configuring CI/CD to generate and publish TechDocs sites - - TechDocs CLI + - Core Plugins + - "How do I install and configure Backstage Developer Portal with plugins." + - Home Page + - Installing and Configuring + - Software Catalog + - Overview + - The life of an Entity + - Catalog Configuration + - System Model + - YAML file format + - Entity Reference + - Well Known annotations + - Well known relations + - Well known statuses + - Creating the catalog graph + - Software Templates + - Overview + - Configuring + - Adding a new template + - Writing a template + - Built in actions + - TechDocs + - Overview + - Getting Started + - Architecture + - Installing and configuring + - Using Cloud Storage for TechDocs generated files + - Configuring CI/CD to generate and publish TechDocs sites + - TechDocs CLI + - Troubleshooting + - Kubernetes + - Installing and Configuring + - Authentication - Troubleshooting - - Kubernetes - - Installing and Configuring - - Authentication - - Troubleshooting - - Search + - Search - Product Manager Guides + - "How do I present Backstage to leadership, what are the benefits, why should I care, etc." - Strategies for adopting - Use cases - User Guides + - "How do I use the default OSS Backstage" - Logging in - Registering a component - Creating a new component From 596dea12617cdec16f7c1b4467a012607823bf58 Mon Sep 17 00:00:00 2001 From: Waldir Montoya Date: Wed, 20 Mar 2024 12:21:20 -0500 Subject: [PATCH 007/567] change BEP number and enhanced Persona description Signed-off-by: Waldir Montoya --- .../README.md | 101 ++++++++++++++---- 1 file changed, 80 insertions(+), 21 deletions(-) rename beps/{0006-docs-personas-framework-portal => 0007-docs-personas-framework-portal}/README.md (67%) diff --git a/beps/0006-docs-personas-framework-portal/README.md b/beps/0007-docs-personas-framework-portal/README.md similarity index 67% rename from beps/0006-docs-personas-framework-portal/README.md rename to beps/0007-docs-personas-framework-portal/README.md index e79d6b11a7..b18e4896ec 100644 --- a/beps/0006-docs-personas-framework-portal/README.md +++ b/beps/0007-docs-personas-framework-portal/README.md @@ -1,8 +1,9 @@ --- -title: Restructuring Backstage Documentation for Improved Navigation and Clarity +title: Enhancing Backstage Documentation, Personas, Framework, and Developer Portal status: provisional authors: - '@waldirmontoya25' + - '@aramissennyeyd' owners: project-areas: - Documentation @@ -20,6 +21,17 @@ creation-date: 2024-03-18 - [Non-Goals](#non-goals) - [Proposal](#proposal) - [Design Details](#design-details) + - [Personas](#personas) + - [User](#user) + - [Documentation Style](#documentation-style) + - [Administrator](#administrator) + - [Documentation Style](#documentation-style-1) + - [Integrator](#integrator) + - [Documentation Style](#documentation-style-2) + - [Contributor](#contributor) + - [Documentation Style](#documentation-style-3) + - [Business Stakeholder](#business-stakeholder) + - [Documentation Style](#documentation-style-4) - [Release Plan](#release-plan) - [Dependencies](#dependencies) - [Example Table of Contents](#example-table-of-contents) @@ -79,40 +91,87 @@ The benefits of restructuring the documentation according to these ideas include - The Docs section of the microsite will be divided into two top-level sections: Framework and Developer Portal. - The structure of the Table of Contents will align with the outline proposed in https://github.com/backstage/backstage/issues/21946. -### User Persona +### Personas -Documentation written for the user persona should be high level, assuming little to no technical knowledge. Concepts like the difference between Backstage Framework and Developer Portal should be explained at a high level to give Users a working knowledge, but should not be flushed out completely until we identify the user as a distinct user persona. Users may also be Administrators, Integrators or Product Managers, but documentation for Users should make no assumptions on that. +#### User -Example explanation of Backstage Framework vs Developer Portal: -"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. Backstage Framework provides the tools you need to build your own developer portal and Backstage Developer Portal is your custom developer portal built using the Backstage Framework." +Users navigate the developer portal to access tools, information, and plugins essential for their daily tasks. They rely on Backstage to effortlessly find resources, utilize integrations, and connect with other tools and services within their ecosystem. Their interaction is predominantly with the frontend of the portal, where ease of use, accessibility, and relevant content discovery are critical. -### Administrator Persona +##### Documentation Style -Documentation written for this persona should be DevOps technical, assuming a strong DevOps background with the exception of a Getting Started section. While we should assume an overall technical knowledge, where possible we should link out to existing strong guides for the technologies we use, ex: PostgreSQL, Docker, Kubernetes, etc. The goal with administrator documentation is to give administrators a strong understanding of how to deploy and manage a Backstage Developer Portal, best practices, and possible tripping points. +Documentation for this persona should be about usability of the portal once it is running. For example: -Example explanation of Backstage Framework vs Developer Portal: -"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. As an administrator, you will be interacting with Backstage Developer Portal primarily -- this is the running Backstage instance that you're managing. It is also useful to have a high level understanding of the Backstage Framework for mitigating issues or better understanding how to scale your Backstage Developer Portal." +- Understanding the mechanics of the Software Catalog +- Registering components +- Deleting components +- How the source of truth is the external tool linked through the plugins +- Understand dependencies relations and the overall schema of the catalog +- Using available scaffolder actions +- Customizing new workflows with available actions +- Searching +- Using available plugins +- Step by step tutorials -### Integrator Persona +#### Administrator -Documentation written for this persona should be software technical, assuming a strong software background with the exception of a Getting Started section. While we can assume an overall technical knowledge, where possible we should link out to useful guides for the technologies we use, ex: Node.js, express.js, React, etc. The goal with documentation written for integrators is to give them a strong understanding of where their work fits into their company's Backstage Developer Portal, orient them to get support from the open source community, and prepare them for continuing to deliver value for their Backstage Developer Portal. +Administrators are responsible for the behind-the-scenes technical setup and maintenance of the Backstage portal. This includes deploying the portal, configuring plugins, managing user access, and ensuring the security and performance of the system. They interact with both the frontend and backend, often using command-line tools, administrative dashboards, and configuration files to perform their tasks. -Example explanation of Backstage Framework vs Developer Portal: -"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. As an integrator, most of your time will be spent working to deliver value on top of the Backstage Framework for your company's Backstage Developer Portal. This means creating plugins, theming your Backstage Developer Portal or adding integrations to internal data stores. You should have a strong understanding of the Backstage Framework and have a strong understaing of code that sits in your Backstage Developer Portal." +##### Documentation Style -### Business Stakeholder +Documentation written for this persona should be DevOps technical, assuming a strong DevOps background. The goal with administrator documentation is to give administrators a strong understanding of how to deploy and manage a Backstage Developer Portal, best practices. For example: -Documentation written for this persona should be strategic, assuming a strong background in business development and strategy. The goal for business documentation is to give a strong understanding of what Backstage Developer Portal can do for their company, how to deliver value quickly and continuously and guides for pitching or driving Backstage adoption. +- Installing and upgrading +- Configuring + - Authentication + - Plugins +- Ingesting data (users/groups/components, etc) +- Installing plugins +- Implement Git Flows for the Developer portal +- Creating Pipelines for Docs generation +- Troubleshooting -Example explanation of Backstage Framework vs Developer Portal: -"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. As a business stakeholder, your time should be spent with your company's Backstage Developer Portal solely. This means understanding the value proposition of an IDP, what improving the developer experience at your company means and what a successful Backstage Developer Portal looks like. The Backstage Framework is the technical underpinning and will generally be invisible to you." +#### Integrator -## Contributor +Integrators actively work on extending and customizing Backstage. This includes developing new plugins, customizing the UI/UX, and integrating external services or data sources. Their work is deeply technical, involving coding, and engaging with the Backstage community for support and collaboration. They need a deep understanding of the Backstage architecture and APIs, working closely with both the framework's backend and frontend aspects. -Documentation written for this role should be technical, but should make no assumptions on technical strength. The goal with this documentation is to onboard new contributors to the technical stack and layout of the project, setting expectations for how to write code, documentation or generally contribute to the library. +##### Documentation Style -Example explanation of Backstage Framework vs Developer Portal: -"Backstage has two meanings, the Backstage Framework and the Backstage Developer Portal. You will work with both the Backstage Framework and Backstage Developer Portal. Backstage Framework is the technical framework for Backstage Developer Portal. The Backstage Developer Portal is the end user for your plugins, documentation or other contributions. As a contributor, you will be working to influence either the Backstage Framework or plugins and Backstage Developer Portals across the world may use your contributions." +Documentation written for this persona should be software technical, assuming a strong software background. While we can assume an overall technical knowledge, where possible we should link out to useful guides for the technologies we use, ex: Node.js, express.js, React, etc. The goal with documentation written for integrators is to give them a strong understanding of how to use the Backstage framework to build/evolve a company's Backstage Developer Portal, orient them to get support from the open source community, and prepare them for continuing to deliver value for their Backstage Developer Portal. For example: + +- API references +- Frontend and Backend systems +- Package architecture +- Extending the Software Catalog +- Creating custom themes +- Integrating new react components +- Building custom authentication providers/strategies +- Accessibility + +#### Contributor + +Contributors are involved in the development of the Backstage framework itself. They contribute to the core codebase, develop new features, fix bugs, create documentation and maintain the overall health of the project. They are deeply involved in the open-source community, collaborating with maintainers and other contributors to improve the framework and its ecosystem. + +##### Documentation Style + +The goal with documentation written for contributors is to give them a strong understanding of how to contribute to the Backstage framework, orient them to get support from the open source community, and prepare them for continuing to deliver value for the Backstage framework. For example: + +- Contributing to the Backstage framework +- Setting up a development environment +- Writing tests +- Writing documentation + +#### Business Stakeholder + +Business stakeholders use Backstage to align technical capabilities with business goals, monitoring how features and plugins support operational efficiency, developer satisfaction, and strategic objectives. They are involved in defining the strategy and measuring the impact of the developer portal on the organization. They need to navigate through dashboards, reports, and analytics within Backstage to gather insights and make informed decisions. + +##### Documentation Style + +Documentation written for this persona should be strategic, assuming a strong background in business development and strategy. The goal for business documentation is to give a strong understanding of what Backstage Developer Portal can do for their company, how to deliver value quickly and continuously and guides for pitching or driving Backstage adoption. For example: + +- Adoption use cases +- Adoption strategies +- Measuring success +- Case studies ## Release Plan From 0b501431e0f528c4c6928b46b0d1372d6a919ea6 Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Thu, 28 Mar 2024 23:36:15 +0100 Subject: [PATCH 008/567] fix(plugins/catalog-backend-module-github): support push events with catalogPath containing glob patterns Signed-off-by: Thomas Cardonne --- .changeset/pink-years-peel.md | 6 ++ .../providers/GithubEntityProvider.test.ts | 71 +++++++++++++++++++ .../src/providers/GithubEntityProvider.ts | 23 +++--- 3 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 .changeset/pink-years-peel.md diff --git a/.changeset/pink-years-peel.md b/.changeset/pink-years-peel.md new file mode 100644 index 0000000000..b2d44e2113 --- /dev/null +++ b/.changeset/pink-years-peel.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +GitHub push events now schedule a refresh on entities that have a refresh_key matching the `catalogPath` config itself. +This allows to support a `catalogPath` configuration that uses glob patterns. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index d0fc54245f..a201177198 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -1074,6 +1074,77 @@ describe('GithubEntityProvider', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); + it('apply refresh call on modified files from push event when catalogPath contains a glob pattern', async () => { + const schedule = new PersistingTaskRunner(); + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + catalogPath: '**/catalog-info.yaml', + }, + }, + }, + }); + + const provider = GithubEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event: EventParams = { + topic: 'github.push', + metadata: { + 'x-github-event': 'push', + }, + eventPayload: { + ref: 'refs/heads/main', + repository: { + name: 'teste-1', + url: 'https://github.com/test-org/test-repo', + default_branch: 'main', + stargazers: 0, + master_branch: 'main', + organization: 'test-org', + topics: [], + }, + created: true, + deleted: false, + forced: false, + commits: [ + { + added: ['new-file.yaml'], + removed: [], + modified: [], + }, + { + added: [], + removed: [], + modified: ['catalog-info.yaml'], + }, + ], + }, + }; + + await provider.onEvent(event); + + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.refresh).toHaveBeenCalledWith({ + keys: [ + 'url:https://github.com/test-org/test-repo/tree/main/catalog-info.yaml', + 'url:https://github.com/test-org/test-repo/blob/main/catalog-info.yaml', + 'url:https://github.com/test-org/test-repo/tree/main/**/catalog-info.yaml', + ], + }); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + }); + it('should process repository when match filters from push event', async () => { const schedule = new PersistingTaskRunner(); const config = new ConfigReader({ diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 3c1e6c390b..b099a597b0 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -374,16 +374,23 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { ); if (modified.length > 0) { + const catalogPath = this.config.catalogPath.startsWith('/') + ? this.config.catalogPath.substring(1) + : this.config.catalogPath; + await this.connection.refresh({ keys: [ - ...modified.map( - filePath => - `url:${event.repository.url}/tree/${branch}/${filePath}`, - ), - ...modified.map( - filePath => - `url:${event.repository.url}/blob/${branch}/${filePath}`, - ), + ...new Set([ + ...modified.map( + filePath => + `url:${event.repository.url}/tree/${branch}/${filePath}`, + ), + ...modified.map( + filePath => + `url:${event.repository.url}/blob/${branch}/${filePath}`, + ), + `url:${event.repository.url}/tree/${branch}/${catalogPath}`, + ]), ], }); } From d779e3b055d8669a77729ba1bc15d61ad132e870 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Sun, 31 Mar 2024 11:46:02 +0530 Subject: [PATCH 009/567] fix not to add git commit link in about card edit button Signed-off-by: npiyush97 --- .changeset/thirty-plums-shout.md | 5 ++ .../AnnotateLocationEntityProcessor.test.ts | 48 +++++++++++++++++++ .../core/AnnotateLocationEntityProcessor.ts | 6 ++- 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 .changeset/thirty-plums-shout.md diff --git a/.changeset/thirty-plums-shout.md b/.changeset/thirty-plums-shout.md new file mode 100644 index 0000000000..5b5ffad6a9 --- /dev/null +++ b/.changeset/thirty-plums-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Added a regex test to check commit hash.If url is from git commit branch ignore the edit url. diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts index 66b6300df9..2656de1c7b 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts @@ -166,5 +166,53 @@ describe('AnnotateLocationEntityProcessor', () => { }, }); }); + it('should not render edit button in about for invalid or git hash commit branch', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + + const location: LocationSpec = { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/catalog-info.yaml', + }; + const originLocation: LocationSpec = { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/catalog-info.yaml', + }; + + const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); + const processor = new AnnotateLocationEntityProcessor({ integrations }); + + expect( + await processor.preProcessEntity( + entity, + location, + () => {}, + originLocation, + ), + ).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/catalog-info.yaml', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/catalog-info.yaml', + 'backstage.io/view-url': + 'https://github.com/backstage/backstage/blob/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/catalog-info.yaml', + 'backstage.io/source-location': + 'url:https://github.com/backstage/backstage/tree/f1ba2bc6097a757c2827ff97fa301cff626de137/packages/app/', + }, + }, + }); + }); }); }); diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index d3329fa86c..1ff4963870 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -53,12 +53,16 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; + const gitCommitBranchURLPattern = /\b[0-9a-f]{5,40}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); viewUrl = location.target; - editUrl = scmIntegration?.resolveEditUrl(location.target); + + if (!gitCommitBranchURLPattern.test(location.target)) { + editUrl = scmIntegration?.resolveEditUrl(location.target); + } const sourceUrl = scmIntegration?.resolveUrl({ url: './', From 735a88aa3a7784692d2d0c9fadc935e2eface976 Mon Sep 17 00:00:00 2001 From: sishwarya <63347232+sishwarya@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:42:29 +0530 Subject: [PATCH 010/567] Create deploy.yaml Signed-off-by: sishwarya <63347232+sishwarya@users.noreply.github.com> --- microsite/data/plugins/deploy.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 microsite/data/plugins/deploy.yaml diff --git a/microsite/data/plugins/deploy.yaml b/microsite/data/plugins/deploy.yaml new file mode 100644 index 0000000000..7d4481eeda --- /dev/null +++ b/microsite/data/plugins/deploy.yaml @@ -0,0 +1,10 @@ +--- +title: Digital.ai Deploy +author: Deploy +authorUrl: https://docs.digital.ai/category/deploy +category: Development +description: Deploy is an agentless deployment automation solution, enabling software development organizations to deploy, upgrade, and rollback complex applications to target environments! +documentation: https://github.com/coder/backstage-plugins/blob/main/plugins/backstage-plugin-coder/README.md +iconUrl: /img/deploy.png +npmPackageName: '@digital.ai/plugin-dai-deploy','@digital.ai/plugin-dai-deploy-backend' +addedDate: '2024-04-8' From f9e4a3d39df7a8ecbfd92e15c4bce65d1f415224 Mon Sep 17 00:00:00 2001 From: sishwarya <63347232+sishwarya@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:45:17 +0530 Subject: [PATCH 011/567] Update deploy.yaml Signed-off-by: sishwarya <63347232+sishwarya@users.noreply.github.com> --- microsite/data/plugins/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/deploy.yaml b/microsite/data/plugins/deploy.yaml index 7d4481eeda..d5359863f0 100644 --- a/microsite/data/plugins/deploy.yaml +++ b/microsite/data/plugins/deploy.yaml @@ -2,7 +2,7 @@ title: Digital.ai Deploy author: Deploy authorUrl: https://docs.digital.ai/category/deploy -category: Development +category: CI/CD description: Deploy is an agentless deployment automation solution, enabling software development organizations to deploy, upgrade, and rollback complex applications to target environments! documentation: https://github.com/coder/backstage-plugins/blob/main/plugins/backstage-plugin-coder/README.md iconUrl: /img/deploy.png From 01968f51495455044f05eac77db17c918dd4f184 Mon Sep 17 00:00:00 2001 From: sishwarya <63347232+sishwarya@users.noreply.github.com> Date: Mon, 8 Apr 2024 17:02:59 +0530 Subject: [PATCH 012/567] Update deploy.yaml Signed-off-by: sishwarya <63347232+sishwarya@users.noreply.github.com> --- microsite/data/plugins/deploy.yaml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/microsite/data/plugins/deploy.yaml b/microsite/data/plugins/deploy.yaml index d5359863f0..c1caea4b48 100644 --- a/microsite/data/plugins/deploy.yaml +++ b/microsite/data/plugins/deploy.yaml @@ -1,10 +1,13 @@ --- -title: Digital.ai Deploy -author: Deploy -authorUrl: https://docs.digital.ai/category/deploy +title: Deploy +author: digital.ai +authorUrl: https://digital.ai/ category: CI/CD description: Deploy is an agentless deployment automation solution, enabling software development organizations to deploy, upgrade, and rollback complex applications to target environments! -documentation: https://github.com/coder/backstage-plugins/blob/main/plugins/backstage-plugin-coder/README.md +documentation: https://github.com/digital-ai/backstage-deploy/blob/main/README.md iconUrl: /img/deploy.png -npmPackageName: '@digital.ai/plugin-dai-deploy','@digital.ai/plugin-dai-deploy-backend' +npmPackageName: '@digital.ai/plugin-dai-deploy' +tags: + - ci + - cd addedDate: '2024-04-8' From 665d118422bf3d61e453bf566f0a3052187cf134 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 14:05:23 -0500 Subject: [PATCH 013/567] feat(openapi-tooling): add breaking changes checks to the verify command Signed-off-by: aramissennyeydd --- packages/repo-tools/cli-report.md | 3 +- packages/repo-tools/src/commands/index.ts | 6 ++- .../commands/repo/schema/openapi/verify.ts | 37 +++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 9d37ce7aff..7c7dd445bc 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -168,7 +168,7 @@ Options: -h, --help Commands: - verify [paths...] + verify [options] [paths...] lint [options] [paths...] test [options] [paths...] fuzz [options] @@ -211,6 +211,7 @@ Options: Usage: backstage-repo-tools repo schema openapi verify [options] [paths...] Options: + --from -h, --help ``` diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index d2fe507941..9f0461e7ea 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -96,7 +96,11 @@ function registerRepoCommand(program: Command) { openApiCommand .command('verify [paths...]') .description( - 'Verify that all OpenAPI schemas are valid and have a matching `schemas/openapi.generated.ts` file.', + 'Verify that all OpenAPI schemas are valid and set up correctly. This also verifies that your API has not changed in a breaking way.', + ) + .option( + '--from ', + 'The base ref to compare against. Defaults to the fork point of the current branch.', ) .action( lazy(() => diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 442494610c..78b5a966c3 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -29,8 +29,10 @@ import { YAML_SCHEMA_PATH, } from '../../../../lib/openapi/constants'; import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers'; +import { exec } from '../../../../lib/exec'; +import { OptionValues } from 'commander'; -async function verify(directoryPath: string) { +async function verify(directoryPath: string, options: OptionValues) { let openapiPath = ''; try { openapiPath = await getPathToOpenApiSpec(directoryPath); @@ -58,10 +60,39 @@ async function verify(directoryPath: string) { `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`, ); } + + let baseRef = options.from ?? process.env.GITHUB_BASE_REF; + if (!baseRef) { + const { stdout: branch } = await exec('git merge-base --fork-point HEAD'); + baseRef = branch.toString().trim(); + } + + try { + const { stdout } = await exec('optic diff', [ + openapiPath, + '--check', + '--base', + baseRef, + ]); + // Log out the results as this still shows API changes that aren't breakages. + console.log( + stdout + .toString() + .split('\n') + .filter(e => !e.startsWith('Rerun') && e.trim()) + .join('\n'), + ); + } catch (err) { + err.message = err.stdout; + throw err; + } } -export async function bulkCommand(paths: string[] = []): Promise { - const resultsList = await runner(paths, dir => verify(dir)); +export async function bulkCommand( + paths: string[] = [], + options: OptionValues, +): Promise { + const resultsList = await runner(paths, dir => verify(dir, options)); let failed = false; for (const { relativeDir, resultText } of resultsList) { From 0b3fac608a9cb8052ad6644380add93c8116b72f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 14:05:30 -0500 Subject: [PATCH 014/567] example breakage Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.generated.ts | 2 +- plugins/catalog-backend/src/schema/openapi.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 550db58f2e..c8c9c73a84 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1072,7 +1072,7 @@ export const spec = { 'application/json': { schema: { type: 'object', - required: ['entityRefs'], + required: ['entityRefs', 'fields'], properties: { entityRefs: { type: 'array', diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index 75dc1d9bbc..d3a65490bf 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -841,6 +841,7 @@ paths: type: object required: - entityRefs + - fields properties: entityRefs: type: array From 7cd15860dc065afd44d1d56e4eb498f7093b7693 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:13:03 -0500 Subject: [PATCH 015/567] testing using the uffizzi workflow Signed-off-by: aramissennyeydd --- .../api-breaking-changes-comment.yml | 111 ++++++++ .github/workflows/api-breaking-changes.yml | 62 +++++ NOTICE | 1 + packages/repo-tools/package.json | 1 + packages/repo-tools/src/commands/index.ts | 28 +- .../commands/package/schema/openapi/check.ts | 93 +++++++ .../src/commands/repo/schema/openapi/check.ts | 80 ++++++ .../commands/repo/schema/openapi/verify.ts | 56 ++-- .../src/lib/openapi/optic/helpers.ts | 244 ++++++++++++++++++ plugins/catalog-backend/package.json | 1 + yarn.lock | 36 +++ 11 files changed, 670 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/api-breaking-changes-comment.yml create mode 100644 .github/workflows/api-breaking-changes.yml create mode 100644 packages/repo-tools/src/commands/package/schema/openapi/check.ts create mode 100644 packages/repo-tools/src/commands/repo/schema/openapi/check.ts create mode 100644 packages/repo-tools/src/lib/openapi/optic/helpers.ts diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml new file mode 100644 index 0000000000..91741bba5b --- /dev/null +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -0,0 +1,111 @@ +name: API Breaking Changes (comment) + +on: + workflow_run: + workflows: + - 'API Breaking Changes (Trigger)' + types: + - completed + +jobs: + setup: + name: Add values from previous step + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + # "If you specify the access for any of these scopes, all of those that are not specified are set to none." + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions + actions: read # Access cache + outputs: + git-ref: ${{ steps.event.outputs.GIT_REF }} + pr-number: ${{ steps.event.outputs.PR_NUMBER }} + action: ${{ steps.event.outputs.ACTION }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + disable-sudo: true + egress-policy: block + allowed-endpoints: > + api.github.com:443 + + - name: 'Download artifacts' + # Fetch output (zip archive) from the workflow run that triggered this workflow. + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == "preview-spec" + })[0]; + if (matchArtifact === undefined) { + throw TypeError('Build Artifact not found!'); + } + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + let fs = require('fs'); + fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data)); + + - name: 'Accept event from first stage' + run: unzip preview-spec.zip event.json + + - name: Read Event into ENV + id: event + run: | + echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_OUTPUT + echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT + echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT + + - name: DEBUG - Print Job Outputs + if: ${{ runner.debug }} + run: | + echo "PR number: ${{ steps.event.outputs.PR_NUMBER }}" + echo "Git Ref: ${{ steps.event.outputs.GIT_REF }}" + echo "Action: ${{ steps.event.outputs.ACTION }}" + cat event.json + + - name: Get Comment + id: get-comment + run: | + unzip preview-spec.zip comment.md + ls + echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT + + add-comment: + name: Write comment about issues + needs: + - setup + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + # Identify comment to be updated + - name: Find comment for Ephemeral Environment + uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 + id: find-comment + with: + issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} + comment-author: 'github-actions[bot]' + body-includes: pr-changes-${{ needs.cache-manifests-file.outputs.pr-number }} + direction: last + + - name: Create or Update Comment with Deployment URL + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ steps.notification.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: comment.md + edit-mode: replace diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml new file mode 100644 index 0000000000..ed2dcd9a0e --- /dev/null +++ b/.github/workflows/api-breaking-changes.yml @@ -0,0 +1,62 @@ +name: API Breaking Changes (Trigger) +on: + pull_request: + types: [opened, synchronize, reopened, closed] + paths-ignore: + - '.changeset/**' + - 'contrib/**' + - 'docs/**' + - 'microsite/**' + - 'beps/**' + - 'scripts/**' + - 'storybook/**' + - '**/*.test.*' + - '**/package.json' + - '*.md' + +jobs: + get-backstage-changes: + env: + NODE_OPTIONS: --max-old-space-size=4096 + name: Build PR image + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} + outputs: + tags: ${{ steps.meta.outputs.tags }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: setup-node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: 18.x + registry-url: https://registry.npmjs.org/ + + - name: yarn install + uses: backstage/actions/yarn-install@a674369920067381b450d398b27df7039b7ef635 # v0.6.5 + with: + cache-prefix: linux-v18 + + - name: breaking changes check + run: | + yarn backstage-repo-tools repo schema openapi check > comment.md + + - name: Upload Rendered Comment as Artifact + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 + with: + name: preview-spec + path: comment.md + retention-days: 2 + + - name: Upload PR Event as Artifact + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: preview-spec + path: ${{ github.event_path }} + retention-days: 2 diff --git a/NOTICE b/NOTICE index fb23d28ebc..1f1dfbccbb 100644 --- a/NOTICE +++ b/NOTICE @@ -5,3 +5,4 @@ Portions of this software were developed by third-party software vendors: - Tech Radar Plugin (https://opensource.zalando.com/tech-radar/), Copyright (c) 2017 Zalando SE - [OpenAPI Generator Templates](./packages/repo-tools/templates), Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) Copyright 2018 SmartBear Software +- Optic CLI (https://github.com/opticdev/optic), Copyright 2022, Optic Labs Corporation diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index abf134b5be..126a392265 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -49,6 +49,7 @@ "@stoplight/spectral-rulesets": "^1.18.0", "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "^14.0.0", + "@useoptic/openapi-utilities": "^0.54.8", "chalk": "^4.0.0", "codeowners-utils": "^1.0.2", "command-exists": "^1.2.9", diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 9f0461e7ea..6a557d909e 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -78,6 +78,14 @@ function registerPackageCommand(program: Command) { .action( lazy(() => import('./package/schema/openapi/fuzz').then(m => m.command)), ); + + openApiCommand + .command('check') + .option('--ignore', 'Ignore linting failures and only log the results.') + .option('--json', 'Output the results as JSON') + .action( + lazy(() => import('./package/schema/openapi/check').then(m => m.command)), + ); } function registerRepoCommand(program: Command) { @@ -96,11 +104,7 @@ function registerRepoCommand(program: Command) { openApiCommand .command('verify [paths...]') .description( - 'Verify that all OpenAPI schemas are valid and set up correctly. This also verifies that your API has not changed in a breaking way.', - ) - .option( - '--from ', - 'The base ref to compare against. Defaults to the fork point of the current branch.', + 'Verify that all OpenAPI schemas are valid and set up correctly.', ) .action( lazy(() => @@ -137,6 +141,20 @@ function registerRepoCommand(program: Command) { .action( lazy(() => import('./repo/schema/openapi/fuzz').then(m => m.command)), ); + + openApiCommand + .command('check') + .description( + 'Check the repository against a specific ref, will run all package `check:api` scripts.', + ) + .option( + '--since ', + 'Check the API against a specific ref', + 'origin/master', + ) + .action( + lazy(() => import('./repo/schema/openapi/check').then(m => m.command)), + ); } export function registerCommands(program: Command) { diff --git a/packages/repo-tools/src/commands/package/schema/openapi/check.ts b/packages/repo-tools/src/commands/package/schema/openapi/check.ts new file mode 100644 index 0000000000..22d2d0a0d6 --- /dev/null +++ b/packages/repo-tools/src/commands/package/schema/openapi/check.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import chalk from 'chalk'; +import { exec } from '../../../../lib/exec'; +import { getPathToCurrentOpenApiSpec } from '../../../../lib/openapi/helpers'; +import { paths as cliPaths } from '../../../../lib/paths'; +import { OptionValues } from 'commander'; +import { env } from 'process'; +import { readFile, rm } from 'fs/promises'; +import { resolve } from 'path'; + +const reduceOpticOutput = (output: string) => { + return output + .split('\n') + .filter(e => !e.startsWith('Rerun') && e.trim()) + .join('\n'); +}; + +async function check(opts: OptionValues) { + const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); + + let baseRef = opts.since ?? process.env.GITHUB_BASE_REF; + if (!baseRef) { + const { stdout: branch } = await exec( + 'git merge-base --fork-point origin/master', + ); + baseRef = branch.toString().trim(); + } + + let failed = false; + let output = ''; + try { + const { stdout } = await exec( + 'yarn optic diff', + [ + resolvedOpenapiPath, + '--check', + opts.json ? '--json' : '', + '--base', + baseRef, + ], + { + cwd: cliPaths.targetRoot, + env: { CI: opts.json ? '1' : undefined, ...env }, + }, + ); + output = stdout.toString(); + } catch (err) { + output = err.stdout; + failed = true; + } + + if (opts.json) { + const file = ( + await readFile(resolve(cliPaths.targetRoot, 'ci-run-details.json')) + ).toString(); + const results = JSON.parse(file); + console.log(file); + if (!opts.ignore && results.failed) { + throw new Error('Some checks failed'); + } + + await rm(resolve(cliPaths.targetRoot, 'ci-run-details.json')); + } else { + console.log(reduceOpticOutput(output)); + if (!opts.ignore && failed) { + throw new Error('Some checks failed'); + } + } +} + +export async function command(opts: OptionValues) { + try { + await check(opts); + if (!opts.json) console.log(chalk.green(`All checks passed.`)); + } catch (err) { + if (!opts.json) console.log(chalk.red(err.message)); + process.exit(1); + } +} diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts new file mode 100644 index 0000000000..f77586a840 --- /dev/null +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -0,0 +1,80 @@ +/* + * 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 { PackageGraph } from '@backstage/cli-node'; +import { OptionValues } from 'commander'; +import { exec } from '../../../../lib/exec'; +import { + CiRunDetails, + generateCompareSummaryMarkdown, +} from '../../../../lib/openapi/optic/helpers'; + +export async function command(opts: OptionValues) { + let packages = await PackageGraph.listTargetPackages(); + if (opts.since) { + const graph = PackageGraph.fromPackages(packages); + const changedPackages = await graph.listChangedPackages({ + ref: opts.since, + analyzeLockfile: true, + }); + const withDevDependents = graph.collectPackageNames( + changedPackages.map(pkg => pkg.name), + pkg => pkg.localDevDependents.keys(), + ); + packages = Array.from(withDevDependents).map(name => graph.get(name)!); + } + + const checkablePackages = packages.filter( + e => e.packageJson.scripts?.['check:api'], + ); + try { + const outputs = { + completed: [], + failed: [], + noop: [], + severity: 0, + } as CiRunDetails; + for (const pkg of checkablePackages) { + const { stdout } = await exec( + 'yarn', + ['check:api', '--ignore', '--json'], + { + cwd: pkg.dir, + }, + ); + const result = JSON.parse(stdout.toString()); + outputs.completed.push(...(result.completed ?? [])); + outputs.failed.push(...(result.failed ?? [])); + outputs.noop.push(...(result.noop ?? [])); + } + + const { stdout: currentSha } = await exec('git', ['rev-parse', 'HEAD']); + console.log( + generateCompareSummaryMarkdown( + { sha: currentSha.toString().trim() }, + outputs, + { verbose: true }, + ), + ); + + const failed = outputs.failed.length > 0; + if (failed) { + throw new Error('Some checks failed'); + } + } catch (err) { + console.error(err); + process.exit(1); + } +} diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 78b5a966c3..2902d99ea4 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -29,17 +29,14 @@ import { YAML_SCHEMA_PATH, } from '../../../../lib/openapi/constants'; import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers'; -import { exec } from '../../../../lib/exec'; -import { OptionValues } from 'commander'; -async function verify(directoryPath: string, options: OptionValues) { - let openapiPath = ''; - try { - openapiPath = await getPathToOpenApiSpec(directoryPath); - } catch { - // Unable to find spec at path. - return; - } +const verifySpecAndGeneratedSpecMatch = async ( + openapiPath: string, + directoryPath: string, +) => { + const openapiTempDirectory = resolvePath(cliPaths.targetDir, '.openapi'); + await fs.mkdirp(openapiTempDirectory); + console.log(openapiTempDirectory); const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8')); await Parser.validate(cloneDeep(yaml) as any); @@ -60,39 +57,22 @@ async function verify(directoryPath: string, options: OptionValues) { `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`, ); } +}; - let baseRef = options.from ?? process.env.GITHUB_BASE_REF; - if (!baseRef) { - const { stdout: branch } = await exec('git merge-base --fork-point HEAD'); - baseRef = branch.toString().trim(); - } - +async function verify(directoryPath: string) { + let openapiPath = ''; try { - const { stdout } = await exec('optic diff', [ - openapiPath, - '--check', - '--base', - baseRef, - ]); - // Log out the results as this still shows API changes that aren't breakages. - console.log( - stdout - .toString() - .split('\n') - .filter(e => !e.startsWith('Rerun') && e.trim()) - .join('\n'), - ); - } catch (err) { - err.message = err.stdout; - throw err; + openapiPath = await getPathToOpenApiSpec(directoryPath); + } catch { + // Unable to find spec at path. + return; } + + await verifySpecAndGeneratedSpecMatch(openapiPath, directoryPath); } -export async function bulkCommand( - paths: string[] = [], - options: OptionValues, -): Promise { - const resultsList = await runner(paths, dir => verify(dir, options)); +export async function bulkCommand(paths: string[] = []): Promise { + const resultsList = await runner(paths, dir => verify(dir)); let failed = false; for (const { relativeDir, resultText } of resultsList) { diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts new file mode 100644 index 0000000000..4c50f9bbd9 --- /dev/null +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -0,0 +1,244 @@ +/* + * 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. + */ +/* eslint-disable no-nested-ternary */ + +import { + compareSpecs, + groupDiffsByEndpoint, + Severity, + getOperationsChangedLabel, + getOperationsChanged, +} from '@useoptic/openapi-utilities'; +import { GroupedDiffs } from '@useoptic/openapi-utilities/build/openapi3/group-diff'; +import { relative } from 'path'; +import { paths as cliPaths } from '../../paths'; + +type Comparison = { + groupedDiffs: ReturnType; + results: Awaited>['results']; +}; + +export type CiRunDetails = { + completed: { + warnings: string[]; + apiName: string; + opticWebUrl?: string | null; + comparison: Comparison; + specUrl?: string | null; + capture?: any; + }[]; + failed: { apiName: string; error: string }[]; + noop: { apiName: string }[]; + severity: Severity; +}; + +const getChecksLabel = ( + results: CiRunDetails['completed'][number]['comparison']['results'], + severity: Severity, +) => { + const totalChecks = results.length; + let failingChecks = 0; + let exemptedFailingChecks = 0; + + for (const result of results) { + if (result.passed) continue; + if (result.severity < severity) continue; + if (result.exempted) exemptedFailingChecks += 1; + else failingChecks += 1; + } + + const exemptedChunk = + exemptedFailingChecks > 0 ? `, ${exemptedFailingChecks} exempted` : ''; + + return failingChecks > 0 + ? `⚠️ **${failingChecks}**/**${totalChecks}** failed${exemptedChunk}` + : totalChecks > 0 + ? `✅ **${totalChecks}** passed${exemptedChunk}` + : `ℹ️ No automated checks have run`; +}; + +function getOperationsText( + groupedDiffs: GroupedDiffs, + options: { webUrl?: string | null; verbose: boolean; labelJoiner?: string }, +) { + const ops = getOperationsChanged(groupedDiffs); + + const operationsText = options.verbose + ? [ + ...[...ops.added].map(o => `\`${o}\` (added)`), + ...[...ops.changed].map(o => `\`${o}\` (changed)`), + ...[...ops.removed].map(o => `\`${o}\` (removed)`), + ].join('\n') + : ''; + return `${getOperationsChangedLabel(groupedDiffs, { + joiner: options.labelJoiner, + })} + + ${operationsText} + `; +} + +const getCaptureIssuesLabel = ({ + unmatchedInteractions, + mismatchedEndpoints, +}: { + unmatchedInteractions: number; + mismatchedEndpoints: number; +}) => { + return [ + ...(unmatchedInteractions + ? [ + `🆕 ${unmatchedInteractions} undocumented path${ + unmatchedInteractions > 1 ? 's' : '' + }`, + ] + : []), + ...(mismatchedEndpoints + ? [ + `⚠️ ${mismatchedEndpoints} mismatch${ + mismatchedEndpoints > 1 ? 'es' : '' + }`, + ] + : []), + ].join('\n'); +}; + +export const generateCompareSummaryMarkdown = ( + commit: { sha: string }, + results: CiRunDetails, + options: { verbose: boolean }, +) => { + const anyCompletedHasWarning = results.completed.some( + s => s.warnings.length > 0, + ); + const anyCompletedHasCapture = results.completed.some(s => s.capture); + return ` + ${ + results.completed.length > 0 + ? `### APIs Changed + + + + + + + + ${anyCompletedHasWarning ? '' : ''} + ${anyCompletedHasCapture ? '' : ''} + + + + + ${results.completed + .map( + s => + ` + + + + + ${anyCompletedHasWarning ? `` : ''} + + ${ + anyCompletedHasCapture + ? ` + + + + ` + : '' + } + `, + ) + .join('\n')} + +
APIChangesRulesWarningsTests
+ + ${relative(cliPaths.targetDir, s.apiName)} + + + + ${getOperationsText(s.comparison.groupedDiffs, { + webUrl: s.opticWebUrl, + verbose: options.verbose, + labelJoiner: ',\n', + })} + + + + ${getChecksLabel(s.comparison.results, results.severity)} + + ${s.warnings.join('\n')} + + ${ + s.capture + ? s.capture.success + ? s.capture.mismatchedEndpoints || s.capture.unmatchedInteractions + ? getCaptureIssuesLabel({ + unmatchedInteractions: s.capture.unmatchedInteractions, + mismatchedEndpoints: s.capture.mismatchedEndpoints, + }) + : `✅ ${s.capture.percentCovered}% coverage` + : '❌ Failed to run' + : '' + } + +
+ ` + : '' + } + ${ + results.failed.length > 0 + ? `### Errors running optic + + + + + + + + + + ${results.failed + .map( + s => ` + + + `, + ) + .join('\n')} + +
APIError
${s.apiName} + + ${'```'} + ${s.error} + ${'```'} + +
+ ` + : '' + } + + Summary of API changes for commit (${commit.sha}) + + ${ + results.noop.length > 0 + ? `${ + results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` + } had no changes.` + : '' + }`; +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 0a20041ee4..058b9318d7 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -42,6 +42,7 @@ ], "scripts": { "build": "backstage-cli package build", + "check:api": "backstage-repo-tools package schema openapi check", "clean": "backstage-cli package clean", "fuzz": "backstage-repo-tools package schema openapi fuzz --exclude-checks response_schema_conformance", "generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/catalog-client", diff --git a/yarn.lock b/yarn.lock index b7a8b0ce2c..b6a138814b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10108,6 +10108,7 @@ __metadata: "@types/is-glob": ^4.0.2 "@types/node": ^18.17.8 "@types/prettier": ^2.0.0 + "@useoptic/openapi-utilities": ^0.54.8 chalk: ^4.0.0 codeowners-utils: ^1.0.2 command-exists: ^1.2.9 @@ -20292,6 +20293,16 @@ __metadata: languageName: node linkType: hard +"@useoptic/json-pointer-helpers@npm:0.54.8": + version: 0.54.8 + resolution: "@useoptic/json-pointer-helpers@npm:0.54.8" + dependencies: + jsonpointer: ^5.0.1 + minimatch: 9.0.3 + checksum: 4eddabb6dce3ca8160dcd4904299b6964945c3fe47d39bfeca6c68b9a50b058b901a6fb10ab168295475d651df3349149faa5f27f77293e15b6eee8d4417432e + languageName: node + linkType: hard + "@useoptic/openapi-io@npm:0.50.10": version: 0.50.10 resolution: "@useoptic/openapi-io@npm:0.50.10" @@ -20346,6 +20357,31 @@ __metadata: languageName: node linkType: hard +"@useoptic/openapi-utilities@npm:^0.54.8": + version: 0.54.8 + resolution: "@useoptic/openapi-utilities@npm:0.54.8" + dependencies: + "@useoptic/json-pointer-helpers": 0.54.8 + ajv: ^8.6.0 + ajv-errors: ~3.0.0 + ajv-formats: ~2.1.0 + chalk: ^4.1.2 + fast-deep-equal: ^3.1.3 + is-url: ^1.2.4 + js-yaml: ^4.1.0 + json-stable-stringify: ^1.0.1 + lodash.groupby: ^4.6.0 + lodash.isequal: ^4.5.0 + lodash.omit: ^4.5.0 + node-machine-id: ^1.1.12 + openapi-types: ^12.0.2 + ts-invariant: ^0.9.3 + url-join: ^4.0.1 + yaml-ast-parser: ^0.0.43 + checksum: fa9e9f430c77687591aaf8b43b7b31a7c2f80fe9c140aaa978f1948f84d3e974181c91c3d8ec3e06efca9735c7826290baf4be72063bf733887aa632b40c3c4a + languageName: node + linkType: hard + "@useoptic/optic@npm:^0.50.10": version: 0.50.10 resolution: "@useoptic/optic@npm:0.50.10" From 7b06c6c78fbd398b1f70b5986448beb810e7ad3c Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:14:17 -0500 Subject: [PATCH 016/567] add attribution comment as well Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- packages/repo-tools/src/lib/openapi/optic/helpers.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index 4c50f9bbd9..cd2a1637d4 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -26,6 +26,11 @@ import { GroupedDiffs } from '@useoptic/openapi-utilities/build/openapi3/group-d import { relative } from 'path'; import { paths as cliPaths } from '../../paths'; +/** + * The below code is copied from https://github.com/opticdev/optic/blob/main/projects/optic/src/commands/ci/comment/common.ts#L82 for use + * with a security flow for forked repositories. + */ + type Comparison = { groupedDiffs: ReturnType; results: Awaited>['results']; From 53c1ec25e221b8d0bd4fb0ded8412f7788938315 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:14:52 -0500 Subject: [PATCH 017/567] update paths requirement Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index ed2dcd9a0e..c6227792c1 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -2,17 +2,8 @@ name: API Breaking Changes (Trigger) on: pull_request: types: [opened, synchronize, reopened, closed] - paths-ignore: - - '.changeset/**' - - 'contrib/**' - - 'docs/**' - - 'microsite/**' - - 'beps/**' - - 'scripts/**' - - 'storybook/**' - - '**/*.test.*' - - '**/package.json' - - '*.md' + paths: + - '**/openapi.yaml' jobs: get-backstage-changes: From fe4c26532bee4514f4cb171780cb4e9333b4fea9 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:22:00 -0500 Subject: [PATCH 018/567] update git command for GA env Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- packages/repo-tools/src/commands/index.ts | 1 + .../repo-tools/src/commands/package/schema/openapi/check.ts | 2 +- packages/repo-tools/src/commands/repo/schema/openapi/check.ts | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 6a557d909e..3fa16aa512 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -83,6 +83,7 @@ function registerPackageCommand(program: Command) { .command('check') .option('--ignore', 'Ignore linting failures and only log the results.') .option('--json', 'Output the results as JSON') + .option('--since ', 'Check the API against a specific ref') .action( lazy(() => import('./package/schema/openapi/check').then(m => m.command)), ); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/check.ts b/packages/repo-tools/src/commands/package/schema/openapi/check.ts index 22d2d0a0d6..3cb0baf313 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/check.ts @@ -32,7 +32,7 @@ const reduceOpticOutput = (output: string) => { async function check(opts: OptionValues) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); - let baseRef = opts.since ?? process.env.GITHUB_BASE_REF; + let baseRef = opts.since; if (!baseRef) { const { stdout: branch } = await exec( 'git merge-base --fork-point origin/master', diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index f77586a840..41d0bec0e5 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -47,9 +47,10 @@ export async function command(opts: OptionValues) { severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { + const baseRef = opts.since ?? process.env.GITHUB_BASE_REF; const { stdout } = await exec( 'yarn', - ['check:api', '--ignore', '--json'], + ['check:api', '--ignore', '--json', '--since', baseRef], { cwd: pkg.dir, }, From b0b1371075e58308dcc7aaef45c6aa8809d12163 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:28:04 -0500 Subject: [PATCH 019/567] use base ref instead Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 2 +- packages/repo-tools/src/commands/repo/schema/openapi/check.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index c6227792c1..fbe4764a9f 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -36,7 +36,7 @@ jobs: - name: breaking changes check run: | - yarn backstage-repo-tools repo schema openapi check > comment.md + yarn backstage-repo-tools repo schema openapi check --since ${{ github.base_ref }} > comment.md - name: Upload Rendered Comment as Artifact uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index 41d0bec0e5..492dd3b5be 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -47,7 +47,7 @@ export async function command(opts: OptionValues) { severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { - const baseRef = opts.since ?? process.env.GITHUB_BASE_REF; + const baseRef = opts.since; const { stdout } = await exec( 'yarn', ['check:api', '--ignore', '--json', '--since', baseRef], From d1b44c9a4af18c380aa0d26884209250c25d0952 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:34:22 -0500 Subject: [PATCH 020/567] get the actual sha and run with that Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../repo-tools/src/commands/repo/schema/openapi/check.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index 492dd3b5be..5599937322 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -23,7 +23,11 @@ import { export async function command(opts: OptionValues) { let packages = await PackageGraph.listTargetPackages(); + + let since = ''; if (opts.since) { + const { stdout: sinceRaw } = await exec('git', ['rev-parse', opts.since]); + since = sinceRaw.toString().trim(); const graph = PackageGraph.fromPackages(packages); const changedPackages = await graph.listChangedPackages({ ref: opts.since, @@ -47,10 +51,10 @@ export async function command(opts: OptionValues) { severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { - const baseRef = opts.since; + const sinceCommands = since ? ['--since', since] : []; const { stdout } = await exec( 'yarn', - ['check:api', '--ignore', '--json', '--since', baseRef], + ['check:api', '--ignore', '--json', ...sinceCommands], { cwd: pkg.dir, }, From dce3d7870dd468a4e7e7daede7e915e1429a4e8f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:38:23 -0500 Subject: [PATCH 021/567] actually check out the needed branches Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index fbe4764a9f..406205d578 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -20,8 +20,13 @@ jobs: with: egress-policy: audit - - name: checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + # Fetch the commit that's merged into the base rather than the target ref + # This will let us diff only the contents of the PR, without fetching more history + ref: 'refs/pull/${{ github.event.pull_request.number }}/merge' + - name: fetch base + run: git fetch --depth 1 origin ${{ github.base_ref }} - name: setup-node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 From e2c9b91fe899feee8308756cafc25af2c18327dd Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:41:06 -0500 Subject: [PATCH 022/567] add origin to base ref Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 406205d578..5c75910546 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,7 +41,7 @@ jobs: - name: breaking changes check run: | - yarn backstage-repo-tools repo schema openapi check --since ${{ github.base_ref }} > comment.md + yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md - name: Upload Rendered Comment as Artifact uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 From 8f681584e8f4b6cbc91732885f322823c819c6b6 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:48:23 -0500 Subject: [PATCH 023/567] update comment to show correctly Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../src/lib/openapi/optic/helpers.ts | 219 +++++++++--------- 1 file changed, 104 insertions(+), 115 deletions(-) diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index cd2a1637d4..9bcd8c45a3 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -131,119 +131,108 @@ export const generateCompareSummaryMarkdown = ( ); const anyCompletedHasCapture = results.completed.some(s => s.capture); return ` - ${ - results.completed.length > 0 - ? `### APIs Changed - - - - - - - - ${anyCompletedHasWarning ? '' : ''} - ${anyCompletedHasCapture ? '' : ''} - - - - - ${results.completed - .map( - s => - ` - - - - - ${anyCompletedHasWarning ? `` : ''} - - ${ - anyCompletedHasCapture - ? ` - - - - ` - : '' - } - `, - ) - .join('\n')} - -
APIChangesRulesWarningsTests
- - ${relative(cliPaths.targetDir, s.apiName)} - - - - ${getOperationsText(s.comparison.groupedDiffs, { - webUrl: s.opticWebUrl, - verbose: options.verbose, - labelJoiner: ',\n', - })} - - - - ${getChecksLabel(s.comparison.results, results.severity)} - - ${s.warnings.join('\n')} - - ${ - s.capture - ? s.capture.success - ? s.capture.mismatchedEndpoints || s.capture.unmatchedInteractions - ? getCaptureIssuesLabel({ - unmatchedInteractions: s.capture.unmatchedInteractions, - mismatchedEndpoints: s.capture.mismatchedEndpoints, - }) - : `✅ ${s.capture.percentCovered}% coverage` - : '❌ Failed to run' - : '' - } - -
- ` - : '' - } - ${ - results.failed.length > 0 - ? `### Errors running optic - - - - - - - - - - ${results.failed - .map( - s => ` - - - `, - ) - .join('\n')} - -
APIError
${s.apiName} - - ${'```'} - ${s.error} - ${'```'} - -
- ` - : '' - } - - Summary of API changes for commit (${commit.sha}) - - ${ - results.noop.length > 0 - ? `${ - results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` - } had no changes.` - : '' - }`; +${ + results.completed.length > 0 + ? `### APIs Changed + + + + + + + +${anyCompletedHasWarning ? '' : ''} +${anyCompletedHasCapture ? '' : ''} + + + +${results.completed + .map( + s => + ` + + + + +${anyCompletedHasWarning ? `` : ''} + +${ + anyCompletedHasCapture + ? ` + +` + : '' +} +`, + ) + .join('\n')} + +
APIChangesRulesWarningsTests
+${relative(cliPaths.targetDir, s.apiName)} + +${getOperationsText(s.comparison.groupedDiffs, { + webUrl: s.opticWebUrl, + verbose: options.verbose, + labelJoiner: ',\n', +})} + +${getChecksLabel(s.comparison.results, results.severity)} +${s.warnings.join('\n')} +${ + s.capture + ? s.capture.success + ? s.capture.mismatchedEndpoints || s.capture.unmatchedInteractions + ? getCaptureIssuesLabel({ + unmatchedInteractions: s.capture.unmatchedInteractions, + mismatchedEndpoints: s.capture.mismatchedEndpoints, + }) + : `✅ ${s.capture.percentCovered}% coverage` + : '❌ Failed to run' + : '' +} +
+` + : '' +} +${ + results.failed.length > 0 + ? `### Errors running optic + + + + + + + + + +${results.failed + .map( + s => ` + + +`, + ) + .join('\n')} + +
APIError
${s.apiName} + +${'```'} +${s.error} +${'```'} + +
+` + : '' +} + +Summary of API changes for commit (${commit.sha}) + +${ + results.noop.length > 0 + ? `${ + results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` + } had no changes.` + : '' +}`; }; From 351ae33e34b8f160d020004548291e1549a72a66 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:30:11 -0500 Subject: [PATCH 024/567] update workflow with improved comment structure Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../api-breaking-changes-comment.yml | 12 +- .github/workflows/api-breaking-changes.yml | 2 - .../src/commands/repo/schema/openapi/check.ts | 62 ++++++++-- .../src/lib/openapi/optic/helpers.ts | 112 +++++++++++++++--- .../src/schema/openapi.generated.ts | 2 +- 5 files changed, 157 insertions(+), 33 deletions(-) diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index 91741bba5b..4c7fa452e7 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -77,7 +77,7 @@ jobs: run: | unzip preview-spec.zip comment.md ls - echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT + grep add-comment: name: Write comment about issues @@ -93,19 +93,19 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 # Identify comment to be updated - - name: Find comment for Ephemeral Environment + - name: Find comment for API Changes uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 id: find-comment with: - issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} + issue-number: ${{ needs.setup.outputs.pr-number }} comment-author: 'github-actions[bot]' - body-includes: pr-changes-${{ needs.cache-manifests-file.outputs.pr-number }} + body-includes: API changes direction: last - - name: Create or Update Comment with Deployment URL + - name: Create or Update Comment with API Changes uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 with: - comment-id: ${{ steps.notification.outputs.comment-id }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body-path: comment.md edit-mode: replace diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5c75910546..07206718bb 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -12,8 +12,6 @@ jobs: name: Build PR image runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} - outputs: - tags: ${{ steps.meta.outputs.tags }} steps: - name: Harden Runner uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index 5599937322..d434e0cd4d 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -20,6 +20,8 @@ import { CiRunDetails, generateCompareSummaryMarkdown, } from '../../../../lib/openapi/optic/helpers'; +import { paths as cliPaths } from '../../../../lib/paths'; +import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; export async function command(opts: OptionValues) { let packages = await PackageGraph.listTargetPackages(); @@ -28,26 +30,34 @@ export async function command(opts: OptionValues) { if (opts.since) { const { stdout: sinceRaw } = await exec('git', ['rev-parse', opts.since]); since = sinceRaw.toString().trim(); - const graph = PackageGraph.fromPackages(packages); - const changedPackages = await graph.listChangedPackages({ - ref: opts.since, - analyzeLockfile: true, - }); - const withDevDependents = graph.collectPackageNames( - changedPackages.map(pkg => pkg.name), - pkg => pkg.localDevDependents.keys(), + const { stdout: changedFilesRaw } = await exec('git', [ + 'diff', + '--name-only', + since, + ]); + const changedFiles = changedFilesRaw.toString().trim(); + + const changedOpenApiSpecs = changedFiles + .split('\n') + .filter(e => e.endsWith(YAML_SCHEMA_PATH)) + .map(e => cliPaths.resolveTarget(e)); + + // filter packages by changedFiles + packages = packages.filter(pkg => + changedOpenApiSpecs.some(e => e.startsWith(`${pkg.dir}/`)), ); - packages = Array.from(withDevDependents).map(name => graph.get(name)!); } const checkablePackages = packages.filter( e => e.packageJson.scripts?.['check:api'], ); + try { const outputs = { completed: [], failed: [], noop: [], + warning: [], severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { @@ -65,6 +75,40 @@ export async function command(opts: OptionValues) { outputs.noop.push(...(result.noop ?? [])); } + for (const pkg of packages.filter( + e => !e.packageJson.scripts?.['check:api'], + )) { + outputs.warning?.push({ + apiName: `${pkg.dir}/`, + warning: 'No check:api script found in package.json', + }); + } + + outputs.completed.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + outputs.failed.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + outputs.noop.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + outputs.warning?.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + const { stdout: currentSha } = await exec('git', ['rev-parse', 'HEAD']); console.log( generateCompareSummaryMarkdown( diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index 9bcd8c45a3..2393e71ce8 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -23,8 +23,6 @@ import { getOperationsChanged, } from '@useoptic/openapi-utilities'; import { GroupedDiffs } from '@useoptic/openapi-utilities/build/openapi3/group-diff'; -import { relative } from 'path'; -import { paths as cliPaths } from '../../paths'; /** * The below code is copied from https://github.com/opticdev/optic/blob/main/projects/optic/src/commands/ci/comment/common.ts#L82 for use @@ -45,6 +43,7 @@ export type CiRunDetails = { specUrl?: string | null; capture?: any; }[]; + warning?: { apiName: string; warning: string }[]; failed: { apiName: string; error: string }[]; noop: { apiName: string }[]; severity: Severity; @@ -121,6 +120,18 @@ const getCaptureIssuesLabel = ({ ].join('\n'); }; +const getBreakagesRow = (breakage: CiRunDetails['completed'][number]) => { + return ` + - ${breakage.apiName} + ${breakage.comparison.results.map( + s => ` + - ${s.where} + ${'```'} + ${s.error} + ${'```'}`, + )}`; +}; + export const generateCompareSummaryMarkdown = ( commit: { sha: string }, results: CiRunDetails, @@ -130,10 +141,61 @@ export const generateCompareSummaryMarkdown = ( s => s.warnings.length > 0, ); const anyCompletedHasCapture = results.completed.some(s => s.capture); - return ` + if ( + results.completed.length === 0 && + results.failed.length === 0 && + results.failed.length === 0 + ) { + return `No API changes detected for commit (${commit.sha})`; + } + const breakages = results.completed + .filter(s => s.comparison.results.some(e => !e.passed)) + .map(e => ({ + ...e, + comparison: { + ...e.comparison, + results: e.comparison.results.filter(f => !f.passed), + }, + })); + const successfullyCompletedCount = + results.completed.length - breakages.length; + return `### Summary for commit (${commit.sha}) + +${ + results.noop.length > 0 + ? `${ + results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` + } had no changes.` + : '' +} +${ + breakages.length > 0 + ? `${ + breakages.length === 1 ? '1 API' : `${breakages.length} APIs` + } had breaking changes.` + : '' +} +${ + successfullyCompletedCount > 0 + ? `${ + successfullyCompletedCount === 1 + ? '1 API' + : `${successfullyCompletedCount} APIs` + } had non-breaking changes.` + : '' +} +${ + results.warning && results.warning.length > 0 + ? `${ + results.warning.length === 1 + ? '1 API' + : `${results.warning.length} APIs` + } had warnings.` + : '' +} ${ results.completed.length > 0 - ? `### APIs Changed + ? `### APIs with Changes @@ -151,7 +213,7 @@ ${results.completed s => `
-${relative(cliPaths.targetDir, s.apiName)} +${s.apiName} ${getOperationsText(s.comparison.groupedDiffs, { @@ -190,13 +252,13 @@ ${ ) .join('\n')} -
-` +` : '' } + ${ results.failed.length > 0 - ? `### Errors running optic + ? `### APIs with Errors @@ -226,13 +288,33 @@ ${'```'} : '' } -Summary of API changes for commit (${commit.sha}) - ${ - results.noop.length > 0 - ? `${ - results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` - } had no changes.` + results.warning && results.warning.length + ? ` +### APIs with Warnings +
+ + + + + + + + ${results.warning + .map(e => ``) + .join('\n')} + +
APIWarning
${e.apiName}${e.warning}
` : '' -}`; +} +${ + breakages.length > 0 + ? ` +### Routes with Breakages + +${breakages.map(getBreakagesRow).join('\n')} +` + : '' +} +`; }; diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index c8c9c73a84..550db58f2e 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1072,7 +1072,7 @@ export const spec = { 'application/json': { schema: { type: 'object', - required: ['entityRefs', 'fields'], + required: ['entityRefs'], properties: { entityRefs: { type: 'array', From 683870a29ada01c4971a5a6efa4ac3c3fe330bc6 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:35:09 -0500 Subject: [PATCH 025/567] add changeset Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .changeset/flat-countries-clap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/flat-countries-clap.md diff --git a/.changeset/flat-countries-clap.md b/.changeset/flat-countries-clap.md new file mode 100644 index 0000000000..ccd779a2b5 --- /dev/null +++ b/.changeset/flat-countries-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': minor +--- + +Adds 2 new commands `repo schema openapi check` and `package schema openapi check`. `repo schema openapi check` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes.They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. From d3d227d44fba792da9cdce1b184e86b137fb45dd Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:36:14 -0500 Subject: [PATCH 026/567] revert catalog changes Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index d3a65490bf..75dc1d9bbc 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -841,7 +841,6 @@ paths: type: object required: - entityRefs - - fields properties: entityRefs: type: array From 9341347dc129e53a343ebcef742a583568dec45c Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:36:33 -0500 Subject: [PATCH 027/567] revert prettier change Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.generated.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 550db58f2e..823ac40a14 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2023 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From f4aebb8e88012d1f9f80f63962b5a7b2ad866971 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:44:06 -0500 Subject: [PATCH 028/567] small refactoring Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../src/commands/repo/schema/openapi/check.ts | 34 ++++--------- .../src/lib/openapi/optic/helpers.ts | 48 +++++++------------ 2 files changed, 26 insertions(+), 56 deletions(-) diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index d434e0cd4d..95bd4dffed 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -23,6 +23,12 @@ import { import { paths as cliPaths } from '../../../../lib/paths'; import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; +function cleanUpApiName(e: { apiName: string }) { + e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, ''); +} + export async function command(opts: OptionValues) { let packages = await PackageGraph.listTargetPackages(); @@ -84,30 +90,10 @@ export async function command(opts: OptionValues) { }); } - outputs.completed.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); - outputs.failed.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); - outputs.noop.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); - outputs.warning?.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); + outputs.completed.forEach(cleanUpApiName); + outputs.failed.forEach(cleanUpApiName); + outputs.noop.forEach(cleanUpApiName); + outputs.warning?.forEach(cleanUpApiName); const { stdout: currentSha } = await exec('git', ['rev-parse', 'HEAD']); console.log( diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index 2393e71ce8..df8ecfc723 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -132,6 +132,14 @@ const getBreakagesRow = (breakage: CiRunDetails['completed'][number]) => { )}`; }; +const addSummaryLine = (items: any[] | number | undefined, label: string) => { + const length = Array.isArray(items) ? items.length : items; + if (!length) return ''; + let text = length === 1 ? `1 API` : `${length} APIs`; + text += ` had ${label}`; + return text; +}; + export const generateCompareSummaryMarkdown = ( commit: { sha: string }, results: CiRunDetails, @@ -161,38 +169,14 @@ export const generateCompareSummaryMarkdown = ( results.completed.length - breakages.length; return `### Summary for commit (${commit.sha}) -${ - results.noop.length > 0 - ? `${ - results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` - } had no changes.` - : '' -} -${ - breakages.length > 0 - ? `${ - breakages.length === 1 ? '1 API' : `${breakages.length} APIs` - } had breaking changes.` - : '' -} -${ - successfullyCompletedCount > 0 - ? `${ - successfullyCompletedCount === 1 - ? '1 API' - : `${successfullyCompletedCount} APIs` - } had non-breaking changes.` - : '' -} -${ - results.warning && results.warning.length > 0 - ? `${ - results.warning.length === 1 - ? '1 API' - : `${results.warning.length} APIs` - } had warnings.` - : '' -} +${addSummaryLine(results.noop, 'no changes')} + +${addSummaryLine(breakages.length, 'breaking changes')} + +${addSummaryLine(successfullyCompletedCount, 'non-breaking changes')} + +${addSummaryLine(results.warning, 'warnings')} + ${ results.completed.length > 0 ? `### APIs with Changes From 61a9e8801f20671e08b9cf64dad2286611a42cbc Mon Sep 17 00:00:00 2001 From: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:13:19 -0400 Subject: [PATCH 029/567] add changeset Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> --- packages/repo-tools/cli-report.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 7c7dd445bc..65c0f50163 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -168,7 +168,7 @@ Options: -h, --help Commands: - verify [options] [paths...] + verify [paths...] lint [options] [paths...] test [options] [paths...] fuzz [options] @@ -179,6 +179,14 @@ Commands: ``` Usage: backstage-repo-tools repo schema openapi fuzz [options] + check [options] + help [command] +``` + +### `backstage-repo-tools repo schema openapi check` + +``` +Usage: backstage-repo-tools repo schema openapi check [options] Options: --since @@ -211,7 +219,6 @@ Options: Usage: backstage-repo-tools repo schema openapi verify [options] [paths...] Options: - --from -h, --help ``` From 5d091a6405dc6bca517f49c2e889e46f256751fd Mon Sep 17 00:00:00 2001 From: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Date: Mon, 1 Apr 2024 19:46:05 -0400 Subject: [PATCH 030/567] revert verify changes Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Signed-off-by: web-next-automation --- .../commands/repo/schema/openapi/verify.ts | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 2902d99ea4..23e07d1230 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -30,14 +30,14 @@ import { } from '../../../../lib/openapi/constants'; import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers'; -const verifySpecAndGeneratedSpecMatch = async ( - openapiPath: string, - directoryPath: string, -) => { - const openapiTempDirectory = resolvePath(cliPaths.targetDir, '.openapi'); - await fs.mkdirp(openapiTempDirectory); - console.log(openapiTempDirectory); - +async function verify(directoryPath: string) { + let openapiPath = ''; + try { + openapiPath = await getPathToOpenApiSpec(directoryPath); + } catch { + // Unable to find spec at path. + return; + } const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8')); await Parser.validate(cloneDeep(yaml) as any); @@ -57,18 +57,6 @@ const verifySpecAndGeneratedSpecMatch = async ( `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`, ); } -}; - -async function verify(directoryPath: string) { - let openapiPath = ''; - try { - openapiPath = await getPathToOpenApiSpec(directoryPath); - } catch { - // Unable to find spec at path. - return; - } - - await verifySpecAndGeneratedSpecMatch(openapiPath, directoryPath); } export async function bulkCommand(paths: string[] = []): Promise { From 490d96829b582c0a707a20d81b8167d94fda5e0d Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Mon, 8 Apr 2024 21:01:31 -0400 Subject: [PATCH 031/567] fix merge issue Signed-off-by: web-next-automation --- packages/repo-tools/src/lib/openapi/optic/helpers.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index df8ecfc723..9f5deb72e0 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -87,9 +87,7 @@ function getOperationsText( ...[...ops.removed].map(o => `\`${o}\` (removed)`), ].join('\n') : ''; - return `${getOperationsChangedLabel(groupedDiffs, { - joiner: options.labelJoiner, - })} + return `${getOperationsChangedLabel(groupedDiffs)} ${operationsText} `; From 5826d70b544472d394b72bc2162a5fbbe8821473 Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Mon, 8 Apr 2024 21:08:15 -0400 Subject: [PATCH 032/567] add cli report Signed-off-by: web-next-automation --- packages/repo-tools/cli-report.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 65c0f50163..68d679fb06 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -172,13 +172,6 @@ Commands: lint [options] [paths...] test [options] [paths...] fuzz [options] - help [command] -``` - -### `backstage-repo-tools repo schema openapi fuzz` - -``` -Usage: backstage-repo-tools repo schema openapi fuzz [options] check [options] help [command] ``` @@ -193,6 +186,16 @@ Options: -h, --help ``` +### `backstage-repo-tools repo schema openapi fuzz` + +``` +Usage: backstage-repo-tools repo schema openapi fuzz [options] + +Options: + --since + -h, --help +``` + ### `backstage-repo-tools repo schema openapi lint` ``` From fd84ca431702a0faa267b475a9a81b8c7d4b4021 Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Mon, 8 Apr 2024 21:09:13 -0400 Subject: [PATCH 033/567] revert Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.generated.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 823ac40a14..550db58f2e 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * 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. From 21caf84d50334e954354ce969034b38ab0556422 Mon Sep 17 00:00:00 2001 From: Ishwarya Surendrababu Date: Mon, 8 Apr 2024 17:37:23 +0530 Subject: [PATCH 034/567] updated the images and deploy.yaml Signed-off-by: Ishwarya Surendrababu --- microsite/data/plugins/deploy.yaml | 4 ++-- microsite/static/img/deploy.svg | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 microsite/static/img/deploy.svg diff --git a/microsite/data/plugins/deploy.yaml b/microsite/data/plugins/deploy.yaml index c1caea4b48..1e2394a920 100644 --- a/microsite/data/plugins/deploy.yaml +++ b/microsite/data/plugins/deploy.yaml @@ -3,9 +3,9 @@ title: Deploy author: digital.ai authorUrl: https://digital.ai/ category: CI/CD -description: Deploy is an agentless deployment automation solution, enabling software development organizations to deploy, upgrade, and rollback complex applications to target environments! +description: The plugin offers integration with Digital.ai Deploy and backstage components and services. It provide access to deployments and reports. documentation: https://github.com/digital-ai/backstage-deploy/blob/main/README.md -iconUrl: /img/deploy.png +iconUrl: /img/deploy.svg npmPackageName: '@digital.ai/plugin-dai-deploy' tags: - ci diff --git a/microsite/static/img/deploy.svg b/microsite/static/img/deploy.svg new file mode 100644 index 0000000000..d0becb98f1 --- /dev/null +++ b/microsite/static/img/deploy.svg @@ -0,0 +1,7 @@ + + + + + + + From ffcf9649f4135fe9e42dc3d3486c775fbb176fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tan=20=F0=9F=94=A5?= Date: Tue, 9 Apr 2024 10:19:49 +0200 Subject: [PATCH 035/567] fix: OpenAPI table in dark mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tan 🔥 --- .../components/OpenApiDefinitionWidget/OpenApiDefinition.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx index ee23e75ff2..1505646dee 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx @@ -34,7 +34,9 @@ const useStyles = makeStyles(theme => ({ [`& .opblock-tag, .opblock-tag small, table thead tr td, - table thead tr th`]: { + table thead tr th, + table tbody tr td, + table tbody tr th`]: { fontFamily: theme.typography.fontFamily, color: theme.palette.text.primary, borderColor: theme.palette.divider, From 725ff0b9bade20e0605eb0f05708ed164980d2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tan=20=F0=9F=94=A5?= Date: Tue, 9 Apr 2024 10:27:57 +0200 Subject: [PATCH 036/567] chore: add Changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tan 🔥 --- .changeset/quiet-boxes-build.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quiet-boxes-build.md diff --git a/.changeset/quiet-boxes-build.md b/.changeset/quiet-boxes-build.md new file mode 100644 index 0000000000..c6c805435b --- /dev/null +++ b/.changeset/quiet-boxes-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': minor +--- + +Fix dark mode text color inside tables in `description:` from OpenAPI definitions From 7da37564ef8584afdd296b8947c42b75c48920ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tania=20R=2E=20Z=C3=BA=C3=B1iga?= Date: Tue, 9 Apr 2024 11:29:23 +0200 Subject: [PATCH 037/567] Update .changeset/quiet-boxes-build.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ben Lambert Signed-off-by: Tania R. Zúñiga --- .changeset/quiet-boxes-build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quiet-boxes-build.md b/.changeset/quiet-boxes-build.md index c6c805435b..c6f802d5b0 100644 --- a/.changeset/quiet-boxes-build.md +++ b/.changeset/quiet-boxes-build.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-api-docs': minor +'@backstage/plugin-api-docs': patch --- Fix dark mode text color inside tables in `description:` from OpenAPI definitions From 0501243a575569e894c8e29949f91b6779fd8259 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Tue, 9 Apr 2024 21:49:45 +0530 Subject: [PATCH 038/567] Enhance Accessibility: Add ARIA Attributes to SearchModal Component Signed-off-by: JeevaRamanathan --- .changeset/thirty-mangos-travel.md | 5 +++++ plugins/search/src/components/SearchModal/SearchModal.tsx | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/thirty-mangos-travel.md diff --git a/.changeset/thirty-mangos-travel.md b/.changeset/thirty-mangos-travel.md new file mode 100644 index 0000000000..2281cc06b6 --- /dev/null +++ b/.changeset/thirty-mangos-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal. diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 814779fb45..07eec4438e 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -190,7 +190,8 @@ export const SearchModal = (props: SearchModalProps) => { paperFullWidth: classes.paperFullWidth, }} onClose={toggleModal} - aria-labelledby="search-modal-title" + aria-label="Search Modal" + aria-modal="true" fullWidth maxWidth="lg" open={open} From fade5e020fe1a3e114262e6bf719753a9a58c31d Mon Sep 17 00:00:00 2001 From: CiscoRob <133238823+CiscoRob@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:38:20 -0500 Subject: [PATCH 039/567] Update CatalogTable to default total count to 0 instead of undefined Depending on latency in making the request the context can load without items and then refresh moments later with the correct count. Until the entities are loaded the total will show as "All (undefined)", which is not confidence inspiring. Signed-off-by: CiscoRob <133238823+CiscoRob@users.noreply.github.com> Signed-off-by: Coderrob --- .../src/components/CatalogTable/CatalogTable.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 674271a044..ba52b7d3a0 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -88,10 +88,15 @@ export const CatalogTable = (props: CatalogTableProps) => { } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const entityListContext = useEntityList(); - const { loading, error, entities, filters, pageInfo, totalItems } = - entityListContext; + const { + loading, + error, + entities, + filters, + pageInfo, + totalItems = 0, + } = entityListContext; const enablePagination = !!pageInfo; - const tableColumns = useMemo( () => typeof columns === 'function' ? columns(entityListContext) : columns, From 411853058ffd516f113f84daf9a467152dea4c4f Mon Sep 17 00:00:00 2001 From: Coderrob Date: Tue, 9 Apr 2024 15:46:11 -0500 Subject: [PATCH 040/567] Add changeset per contributor guide Change display to avoid displaying counts when not loaded yet Signed-off-by: Coderrob --- .changeset/swift-humans-hunt.md | 5 ++++ .../CatalogTable/CatalogTable.test.tsx | 4 ++-- .../components/CatalogTable/CatalogTable.tsx | 23 ++++++++++--------- 3 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 .changeset/swift-humans-hunt.md diff --git a/.changeset/swift-humans-hunt.md b/.changeset/swift-humans-hunt.md new file mode 100644 index 0000000000..b96b3e41bc --- /dev/null +++ b/.changeset/swift-humans-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Avoiding pre-loading display total count undefined for table counts diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index 052d8f4787..35639069df 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -343,7 +343,7 @@ describe('CatalogTable component', () => { expect(screen.getByText('Should be rendered')).toBeInTheDocument(); }); - it('should render the label column with customised title and value as specified', async () => { + it('should render the label column with customized title and value as specified', async () => { const columns = [ CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), @@ -381,7 +381,7 @@ describe('CatalogTable component', () => { expect(labelCellValue).toBeInTheDocument(); }); - it('should render the label column with customised title and value as specified using function', async () => { + it('should render the label column with customized title and value as specified using function', async () => { const columns: CatalogTableColumnsFunc = ({ filters, entities: entities1, diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index ba52b7d3a0..08da250051 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -88,14 +88,8 @@ export const CatalogTable = (props: CatalogTableProps) => { } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const entityListContext = useEntityList(); - const { - loading, - error, - entities, - filters, - pageInfo, - totalItems = 0, - } = entityListContext; + const { loading, error, entities, filters, pageInfo, totalItems } = + entityListContext; const enablePagination = !!pageInfo; const tableColumns = useMemo( () => @@ -175,13 +169,20 @@ export const CatalogTable = (props: CatalogTableProps) => { const currentKind = filters.kind?.value || ''; const currentType = filters.type?.value || ''; + const currentCount = Number.isSafeInteger(totalItems) + ? `(${totalItems})` + : ''; // TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar const titlePreamble = capitalize(filters.user?.value ?? 'all'); - const titleDisplay = [titlePreamble, currentType, pluralize(currentKind)] + const title = [ + titlePreamble, + currentType, + pluralize(currentKind), + currentCount, + ] .filter(s => s) .join(' '); - const title = `${titleDisplay} (${totalItems})`; const actions = props.actions || defaultActions; const options = { actionsColumnIndex: -1, @@ -197,7 +198,7 @@ export const CatalogTable = (props: CatalogTableProps) => { columns={tableColumns} emptyContent={emptyContent} isLoading={loading} - title={titleDisplay} + title={title} actions={actions} subtitle={subtitle} options={options} From 2ab814f1246b1d0d50b81f20f6acf2dc8e9e93d0 Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Wed, 10 Apr 2024 23:04:50 +0200 Subject: [PATCH 041/567] add more known styles Signed-off-by: Juan Pablo Garcia Ripa --- .../no-top-level-material-ui-4-imports.js | 18 +++++++++++++++++- .../no-top-level-material-ui-4-imports.test.ts | 8 +++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js index 77adcad215..fa13c0367e 100644 --- a/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js +++ b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js @@ -27,7 +27,23 @@ */ const KNOWN_STYLES = [ - // TODO: add exports from colorManipulator and transitions + // colorManipulator + 'hexToRgb', + 'rgbToHex', + 'hslToRgb', + 'decomposeColor', + 'recomposeColor', + 'getContrastRatio', + 'getLuminance', + 'emphasize', + 'fade', + 'alpha', + 'darken', + 'lighten', + // transitions + 'easing', + 'duration', + // styles 'createTheme', 'unstable_createMuiStrictModeTheme', 'createMuiTheme', diff --git a/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts index 2d29c2f9e9..0fe8b09364 100644 --- a/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts +++ b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts @@ -93,6 +93,8 @@ import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`, ThemeProvider, WithStyles, Tooltip as MaterialTooltip, + alpha, + easing } from '@material-ui/core';`, errors: [{ messageId: 'topLevelImport' }], output: `import Box from '@material-ui/core/Box'; @@ -101,7 +103,7 @@ import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; import Grid from '@material-ui/core/Grid'; import MaterialTooltip from '@material-ui/core/Tooltip'; -import { makeStyles, ThemeProvider, WithStyles } from '@material-ui/core/styles';`, +import { makeStyles, ThemeProvider, WithStyles, alpha, easing } from '@material-ui/core/styles';`, }, { code: `import { Box, Button, makeStyles } from '@material-ui/core';`, @@ -111,11 +113,11 @@ import Button from '@material-ui/core/Button'; import { makeStyles } from '@material-ui/core/styles';`, }, { - code: `import { Paper, Typography, styled, withStyles } from '@material-ui/core';`, + code: `import { Paper, Typography, styled, withStyles, alpha, duration} from '@material-ui/core';`, errors: [{ messageId: 'topLevelImport' }], output: `import Paper from '@material-ui/core/Paper'; import Typography from '@material-ui/core/Typography'; -import { styled, withStyles } from '@material-ui/core/styles';`, +import { styled, withStyles, alpha, duration } from '@material-ui/core/styles';`, }, { code: `import { styled } from '@material-ui/core';`, From c56cfd89906d28a40e43737a7f1cde386f162e3e Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Wed, 10 Apr 2024 23:09:21 +0200 Subject: [PATCH 042/567] remove harcoded module Signed-off-by: Juan Pablo Garcia Ripa --- .../rules/no-top-level-material-ui-4-imports.js | 8 ++++---- .../src/no-top-level-material-ui-4-imports.test.ts | 7 +++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js index fa13c0367e..adf04492b4 100644 --- a/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js +++ b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js @@ -204,7 +204,7 @@ module.exports = { if (specifier.emitProp && !specifier.emitComponent) { const replacement = `import { ${getNamedImportValue( specifier, - )} } from '@material-ui/core/${specifier.componentValue}';`; + )} } from '${node.source.value}/${specifier.componentValue}';`; replacements.push(replacement); } @@ -213,9 +213,9 @@ module.exports = { replacements.push( `import ${ specifier.componentAlias ?? specifier.componentValue - }, { ${getNamedImportValue( - specifier, - )} } from '@material-ui/core/${specifier.componentValue}';`, + }, { ${getNamedImportValue(specifier)} } from '${ + node.source.value + }/${specifier.componentValue}';`, ); } } diff --git a/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts index 0fe8b09364..fdde615c3e 100644 --- a/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts +++ b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts @@ -154,5 +154,12 @@ import { styled, withStyles, alpha, duration } from '@material-ui/core/styles';` errors: [{ messageId: 'topLevelImport' }], output: `import { styled as s } from '@material-ui/core/styles';`, }, + { + code: `import { TreeItem, TreeItemProps, TreeView, AlertProps } from '@material-ui/lab';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import TreeItem, { TreeItemProps } from '@material-ui/lab/TreeItem'; +import TreeView from '@material-ui/lab/TreeView'; +import { AlertProps } from '@material-ui/lab/Alert';`, + }, ], }); From 6c317a74d3d720fd28923928a70a1d8d8c14787a Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Fri, 5 Apr 2024 18:09:09 +0200 Subject: [PATCH 043/567] Update pink-years-peel.md Signed-off-by: Thomas Cardonne --- .changeset/pink-years-peel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pink-years-peel.md b/.changeset/pink-years-peel.md index b2d44e2113..339889f27e 100644 --- a/.changeset/pink-years-peel.md +++ b/.changeset/pink-years-peel.md @@ -2,5 +2,5 @@ '@backstage/plugin-catalog-backend-module-github': patch --- -GitHub push events now schedule a refresh on entities that have a refresh_key matching the `catalogPath` config itself. +GitHub push events now schedule a refresh on entities that have a `refresh_key` matching the `catalogPath` config itself. This allows to support a `catalogPath` configuration that uses glob patterns. From 2eb7b4f1d61b66b346c7b4c71c84cb8c42b28b66 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Sat, 13 Apr 2024 17:39:39 +0200 Subject: [PATCH 044/567] updating getting-started permissions docs to new backend Signed-off-by: Peter Macdonald --- docs/permissions/getting-started.md | 146 ++++++++++++---------------- 1 file changed, 60 insertions(+), 86 deletions(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index e241f0e38e..de092dce01 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -20,20 +20,12 @@ The permissions framework depends on a few other Backstage systems, which must b The permissions framework itself is new to Backstage and still evolving quickly. To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! -### Enable service-to-service authentication - -Service-to-service authentication allows Backstage backend code to verify that a given request originates from elsewhere in the Backstage backend. This is useful for tasks like collation of catalog entities in the search index. This type of request shouldn’t be permissioned, so it’s important to configure this feature before trying to use the permissions framework. - -To set up service-to-service authentication, follow the [service-to-service authentication docs](../auth/service-to-service-auth.md). - ### Supply an identity resolver to populate group membership on sign in **Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up. Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity. -[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. - ## Optionally add cookie-based authentication Asset requests initiated by the browser will not include a token in the `Authorization` header. If these requests check authorization through the permission framework, as done in plugins like TechDocs, then you'll need to set up cookie-based authentication. Refer to the ["Authenticate API requests"](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial for a demonstration on how to implement this behavior. @@ -44,68 +36,32 @@ Asset requests initiated by the browser will not include a token in the `Authori The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: -1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend: +1. Add `@backstage/plugin-permission-backend` and `@backstage/plugin-permission-backend-module-allow-all-policy` to your backend dependencies, this will add the permission backend and a policy that allows all permissions: ```bash # From your Backstage root directory yarn --cwd packages/backend add @backstage/plugin-permission-backend ``` -2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. - - ```typescript title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; - import { - AuthorizeResult, - PolicyDecision, - } from '@backstage/plugin-permission-common'; - import { PermissionPolicy } from '@backstage/plugin-permission-node'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - class TestPermissionPolicy implements PermissionPolicy { - async handle(): Promise { - return { result: AuthorizeResult.ALLOW }; - } - } - - export default async function createPlugin( - env: PluginEnvironment, - ): Promise { - return await createRouter({ - config: env.config, - logger: env.logger, - discovery: env.discovery, - policy: new TestPermissionPolicy(), - identity: env.identity, - }); - } + ```bash + # From your Backstage root directory + yarn --cwd packages/backend add @backstage/plugin-permission-backend-module-allow-all-policy ``` -3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app: +2. Add the following to `packages/backend/src/index.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. - ```ts title="packages/backend/src/index.ts" - import proxy from './plugins/proxy'; - import techdocs from './plugins/techdocs'; - import search from './plugins/search'; + ```typescript title="packages/backend/src/index.ts" + import { createBackend } from '@backstage/backend-defaults'; + const backend = createBackend(); + // ... /* highlight-add-next-line */ - import permission from './plugins/permission'; - - async function main() { - const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const permissionEnv = useHotMemoize(module, () => createEnv('permission')); - // .. - - apiRouter.use('/techdocs', await techdocs(techdocsEnv)); - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - /* highlight-add-next-line */ - apiRouter.use('/permission', await permission(permissionEnv)); - // .. - } + backend.add(import('@backstage/plugin-permission-backend/alpha')); + /* highlight-add-next-line */ + backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), + ); + // ... + backend.start(); ``` ### 2. Enable and test the permissions system @@ -119,43 +75,61 @@ Now that the permission backend is running, it’s time to enable the permission enabled: true ``` -2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: +2. Its now all wired up and working, great! But perhaps we don't want to simply allow everything, lets try and create our own policy, to do this you can create a new folder in `packages/backend/src` called `permissions` and create a new file called `policy.ts`, in that file we can add the following to create a policy that denies deleting entities from the catalog: - ```ts title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; + ```ts title="packages/backend/src/permissions/policy.ts" import { AuthorizeResult, PolicyDecision, } from '@backstage/plugin-permission-common'; - /* highlight-remove-next-line */ - import { PermissionPolicy } from '@backstage/plugin-permission-node'; - /* highlight-add-start */ import { PermissionPolicy, PolicyQuery, } from '@backstage/plugin-permission-node'; - /* highlight-add-end */ - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(): Promise { - /* highlight-add-start */ - async handle(request: PolicyQuery): Promise { - if (request.permission.name === 'catalog.entity.delete') { - return { - result: AuthorizeResult.DENY, - }; - } - /* highlight-add-end */ - - return { result: AuthorizeResult.ALLOW }; - } - } ``` -3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. +export class DenyCatalogDeletePolicy implements PermissionPolicy { +async handle(request: PolicyQuery): Promise { +if (request.permission.name === 'catalog.entity.delete') { +return { +result: AuthorizeResult.DENY, +}; +} + + return { result: AuthorizeResult.ALLOW }; + } + +} + +```` +3. We then need to use the permissions backend policy extension point to register our policy, to do this we can add the following to `packages/backend/src/index.ts`: + +```ts title="packages/backend/src/index.ts" +import { createBackend } from '@backstage/backend-defaults'; +import { DenyCatalogDeletePolicy } from './permissions/policy'; +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-permission-backend/alpha')); +/* highlight-remove-next-line */ +backend.add(import('@backstage/plugin-permission-backend-module-allow-all-policy')); + /* highlight-add-next-line */ + backend.add(createBackendModule({ + pluginId: 'permission', + moduleId: 'deny-catalog-delete-policy', + register(reg){ + reg.registerInit({ + deps: { policy: policyExtensionPoint }, + async init({ policy }) { + policy.setPolicy(new DenyCatalogDeletePolicy()); + } + }) + } +})); +// ... +backend.start(); +```` + +4. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. ![Entity detail page showing disabled unregister entity context menu entry](../assets/permissions/disabled-unregister-entity.png) From d76cb295d4018bd1fb0a4667dd45de011429d5fc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 20:36:23 +0000 Subject: [PATCH 045/567] chore(deps): update dependency ts-morph to v22 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .changeset/renovate-228c530.md | 5 ++++ plugins/bitbucket-cloud-common/package.json | 2 +- yarn.lock | 30 ++++++++++----------- 3 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 .changeset/renovate-228c530.md diff --git a/.changeset/renovate-228c530.md b/.changeset/renovate-228c530.md new file mode 100644 index 0000000000..e1838b3481 --- /dev/null +++ b/.changeset/renovate-228c530.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-bitbucket-cloud-common': patch +--- + +Updated dependency `ts-morph` to `^22.0.0`. diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 12bf713f11..4935aa3347 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -44,6 +44,6 @@ "@backstage/cli": "workspace:^", "@openapitools/openapi-generator-cli": "^2.4.26", "msw": "^1.0.0", - "ts-morph": "^21.0.0" + "ts-morph": "^22.0.0" } } diff --git a/yarn.lock b/yarn.lock index 6420fa6e3a..f59e37375c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5389,7 +5389,7 @@ __metadata: "@openapitools/openapi-generator-cli": ^2.4.26 cross-fetch: ^4.0.0 msw: ^1.0.0 - ts-morph: ^21.0.0 + ts-morph: ^22.0.0 languageName: unknown linkType: soft @@ -18188,15 +18188,15 @@ __metadata: languageName: node linkType: hard -"@ts-morph/common@npm:~0.22.0": - version: 0.22.0 - resolution: "@ts-morph/common@npm:0.22.0" +"@ts-morph/common@npm:~0.23.0": + version: 0.23.0 + resolution: "@ts-morph/common@npm:0.23.0" dependencies: fast-glob: ^3.3.2 minimatch: ^9.0.3 mkdirp: ^3.0.1 path-browserify: ^1.0.1 - checksum: e549facfff2a68eeef4e3e2c4183e7216a02b57e62cdfe60ca15d5fdee24770bd3b5b6d1a0388cfce7b4dfaeb0ebe31ffa40585e36b9fb7948aea8081fa73769 + checksum: 96463742ec1114900901ded8aecc2c9664b20119454a56c896042e6a5e5b153af1d986467362d737ed0130506aeac9655731922dc8c4e851a16f9c1a8a8099b4 languageName: node linkType: hard @@ -23566,10 +23566,10 @@ __metadata: languageName: node linkType: hard -"code-block-writer@npm:^12.0.0": - version: 12.0.0 - resolution: "code-block-writer@npm:12.0.0" - checksum: 9f6505a4d668c9131c6f3f686359079439e66d5f50c236614d52fcfa53aeb0bc615b2c6c64ef05b5511e3b0433ccfd9f7756ad40eb3b9298af6a7d791ab1981d +"code-block-writer@npm:^13.0.1": + version: 13.0.1 + resolution: "code-block-writer@npm:13.0.1" + checksum: 678b740d1723c7cc3c5addcbc1a91d9a7a3f033510eb8e0639154fcaae456c80630dbd40d16aefdffaf3edb5ffb352d7d46f195f69c8be692c4d7debb1dc7933 languageName: node linkType: hard @@ -44024,13 +44024,13 @@ __metadata: languageName: node linkType: hard -"ts-morph@npm:^21.0.0": - version: 21.0.1 - resolution: "ts-morph@npm:21.0.1" +"ts-morph@npm:^22.0.0": + version: 22.0.0 + resolution: "ts-morph@npm:22.0.0" dependencies: - "@ts-morph/common": ~0.22.0 - code-block-writer: ^12.0.0 - checksum: f8e6acd4cdb2842af47ccf4e8900dc3f230f20c3b0d28e1e8b58c395b0a16d7b3e03ef56f29da3fdb861c50e22eb52524e0fc4bfca0fde8448f81b8f4f6aa157 + "@ts-morph/common": ~0.23.0 + code-block-writer: ^13.0.1 + checksum: 7bf0ec8cc9ab2a4ce528ec249634db315caa01b5783259484fd91ac0dae2f05b94d9c53ba236939fad2b8ab882e7fce1c82a689ab7c9a4a9dcec5c1160d77264 languageName: node linkType: hard From 7246fba8ec04caf0bd693773cce2a16bb12416fb Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Mon, 15 Apr 2024 15:18:17 +0200 Subject: [PATCH 046/567] removes old youtube video Signed-off-by: Peter Macdonald --- docs/permissions/getting-started.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index de092dce01..bd4294caad 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -4,12 +4,6 @@ title: Getting Started description: How to get started with the permission framework as an integrator --- -If you prefer to watch a video instead, you can start with this video introduction: - - - -> Note: This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. - Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. ## Prerequisites From cf3ec13839638d865fbfc186648fe207fc006c03 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Mon, 15 Apr 2024 15:23:13 +0200 Subject: [PATCH 047/567] weird formatting fix Signed-off-by: Peter Macdonald --- docs/permissions/getting-started.md | 120 ++++++++++++++-------------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index bd4294caad..ef2194a91c 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -32,31 +32,31 @@ The permissions framework uses a new `permission-backend` plugin to accept autho 1. Add `@backstage/plugin-permission-backend` and `@backstage/plugin-permission-backend-module-allow-all-policy` to your backend dependencies, this will add the permission backend and a policy that allows all permissions: - ```bash - # From your Backstage root directory - yarn --cwd packages/backend add @backstage/plugin-permission-backend - ``` +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-permission-backend +``` - ```bash - # From your Backstage root directory - yarn --cwd packages/backend add @backstage/plugin-permission-backend-module-allow-all-policy - ``` +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-permission-backend-module-allow-all-policy +``` 2. Add the following to `packages/backend/src/index.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. - ```typescript title="packages/backend/src/index.ts" - import { createBackend } from '@backstage/backend-defaults'; - const backend = createBackend(); - // ... - /* highlight-add-next-line */ - backend.add(import('@backstage/plugin-permission-backend/alpha')); - /* highlight-add-next-line */ - backend.add( - import('@backstage/plugin-permission-backend-module-allow-all-policy'), - ); - // ... - backend.start(); - ``` +```typescript title="packages/backend/src/index.ts" +import { createBackend } from '@backstage/backend-defaults'; +const backend = createBackend(); +// ... +/* highlight-add-next-line */ +backend.add(import('@backstage/plugin-permission-backend/alpha')); +/* highlight-add-next-line */ +backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), +); +// ... +backend.start(); +``` ### 2. Enable and test the permissions system @@ -64,38 +64,36 @@ Now that the permission backend is running, it’s time to enable the permission 1. Set the property `permission.enabled` to `true` in `app-config.yaml`. - ```yaml title="app-config.yaml" - permission: - enabled: true - ``` +```yaml title="app-config.yaml" +permission: + enabled: true +``` 2. Its now all wired up and working, great! But perhaps we don't want to simply allow everything, lets try and create our own policy, to do this you can create a new folder in `packages/backend/src` called `permissions` and create a new file called `policy.ts`, in that file we can add the following to create a policy that denies deleting entities from the catalog: - ```ts title="packages/backend/src/permissions/policy.ts" - import { - AuthorizeResult, - PolicyDecision, - } from '@backstage/plugin-permission-common'; - import { - PermissionPolicy, - PolicyQuery, - } from '@backstage/plugin-permission-node'; - ``` +```ts title="packages/backend/src/permissions/policy.ts" +import { + AuthorizeResult, + PolicyDecision, +} from '@backstage/plugin-permission-common'; +import { + PermissionPolicy, + PolicyQuery, +} from '@backstage/plugin-permission-node'; export class DenyCatalogDeletePolicy implements PermissionPolicy { -async handle(request: PolicyQuery): Promise { -if (request.permission.name === 'catalog.entity.delete') { -return { -result: AuthorizeResult.DENY, -}; -} - - return { result: AuthorizeResult.ALLOW }; + async handle(request: PolicyQuery): Promise { + if (request.permission.name === 'catalog.entity.delete') { + return { + result: AuthorizeResult.DENY, + }; } + return { result: AuthorizeResult.ALLOW }; + } } +``` -```` 3. We then need to use the permissions backend policy extension point to register our policy, to do this we can add the following to `packages/backend/src/index.ts`: ```ts title="packages/backend/src/index.ts" @@ -105,23 +103,27 @@ const backend = createBackend(); // ... backend.add(import('@backstage/plugin-permission-backend/alpha')); /* highlight-remove-next-line */ -backend.add(import('@backstage/plugin-permission-backend-module-allow-all-policy')); - /* highlight-add-next-line */ - backend.add(createBackendModule({ - pluginId: 'permission', - moduleId: 'deny-catalog-delete-policy', - register(reg){ - reg.registerInit({ - deps: { policy: policyExtensionPoint }, - async init({ policy }) { - policy.setPolicy(new DenyCatalogDeletePolicy()); - } - }) - } -})); +backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), +); +/* highlight-add-next-line */ +backend.add( + createBackendModule({ + pluginId: 'permission', + moduleId: 'deny-catalog-delete-policy', + register(reg) { + reg.registerInit({ + deps: { policy: policyExtensionPoint }, + async init({ policy }) { + policy.setPolicy(new DenyCatalogDeletePolicy()); + }, + }); + }, + }), +); // ... backend.start(); -```` +``` 4. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. From 7c1540d46ed22ca1980e0fa89640c72ca103d3a4 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Tue, 16 Apr 2024 15:09:22 +0530 Subject: [PATCH 048/567] checking url is sha1 hash of len 40 Signed-off-by: npiyush97 --- .../src/modules/core/AnnotateLocationEntityProcessor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index 1ff4963870..83638d205f 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -53,7 +53,7 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; - const gitCommitBranchURLPattern = /\b[0-9a-f]{5,40}\b/; + const gitCommitBranchURLPattern = /\b[0-9a-f]{40,}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); From 18f565273a1eb0d30e1b6fc0735ad5df985e2016 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 16 Apr 2024 20:15:35 +0200 Subject: [PATCH 049/567] fixing highlight line Signed-off-by: Peter Macdonald --- docs/permissions/getting-started.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index ef2194a91c..5f8db5556e 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -106,7 +106,7 @@ backend.add(import('@backstage/plugin-permission-backend/alpha')); backend.add( import('@backstage/plugin-permission-backend-module-allow-all-policy'), ); -/* highlight-add-next-line */ +/* highlight-add-start */ backend.add( createBackendModule({ pluginId: 'permission', @@ -121,6 +121,7 @@ backend.add( }, }), ); +/* highlight-add-end */ // ... backend.start(); ``` From 9b7dfe7ea98bc6bd30daaa511b96e47b08c36db0 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 17 Apr 2024 17:04:13 +0200 Subject: [PATCH 050/567] improve TechDocsSearch Signed-off-by: Kiss Miklos --- plugins/techdocs/src/search/components/TechDocsSearch.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/search/components/TechDocsSearch.tsx b/plugins/techdocs/src/search/components/TechDocsSearch.tsx index 665f99c1bb..ce77d547db 100644 --- a/plugins/techdocs/src/search/components/TechDocsSearch.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearch.tsx @@ -63,6 +63,7 @@ const TechDocsSearchBar = (props: TechDocsSearchProps) => { const navigate = useNavigate(); const { setFilters, + term, result: { loading, value: searchVal }, } = useSearch(); const [options, setOptions] = useState([]); @@ -109,7 +110,7 @@ const TechDocsSearchBar = (props: TechDocsSearchProps) => { ''} filterOptions={x => { return x; // This is needed to get renderOption to be called after options change. Bug in material-ui? @@ -117,7 +118,7 @@ const TechDocsSearchBar = (props: TechDocsSearchProps) => { onClose={() => { setOpen(false); }} - onFocus={() => { + onOpen={() => { setOpen(true); }} onChange={handleSelection} From 4039d328ae08f51b550c0d6b191776b004335446 Mon Sep 17 00:00:00 2001 From: Coderrob Date: Wed, 17 Apr 2024 11:14:53 -0500 Subject: [PATCH 051/567] Adjust to make lighthouse happier Signed-off-by: Coderrob --- plugins/catalog/src/components/CatalogTable/CatalogTable.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 08da250051..b1590bdff3 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -169,9 +169,7 @@ export const CatalogTable = (props: CatalogTableProps) => { const currentKind = filters.kind?.value || ''; const currentType = filters.type?.value || ''; - const currentCount = Number.isSafeInteger(totalItems) - ? `(${totalItems})` - : ''; + const currentCount = typeof totalItems === 'number' ? `(${totalItems})` : ''; // TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar const titlePreamble = capitalize(filters.user?.value ?? 'all'); const title = [ From 1256d8813d3fc5ad0eca36d78b66825ca6c48b1e Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 17 Apr 2024 18:37:35 +0200 Subject: [PATCH 052/567] add changeset Signed-off-by: Kiss Miklos --- .changeset/slimy-fans-raise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slimy-fans-raise.md diff --git a/.changeset/slimy-fans-raise.md b/.changeset/slimy-fans-raise.md new file mode 100644 index 0000000000..782ef4bc82 --- /dev/null +++ b/.changeset/slimy-fans-raise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fix weird opening behaviour of the component. From 88191bb54ebf9e118f3c5fbbd2a7ab7b1e669bd6 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Wed, 17 Apr 2024 17:39:41 -0300 Subject: [PATCH 053/567] bugfix: Proper error thrown in plugin-azure-devops-backend when gitRepository is not found. Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .changeset/three-sheep-remember.md | 5 +++ .../src/api/AzureDevOpsApi.test.ts | 37 +++++++++++++++++++ .../src/api/AzureDevOpsApi.ts | 20 ++++++++++ 3 files changed, 62 insertions(+) create mode 100644 .changeset/three-sheep-remember.md diff --git a/.changeset/three-sheep-remember.md b/.changeset/three-sheep-remember.md new file mode 100644 index 0000000000..51ffc8e3db --- /dev/null +++ b/.changeset/three-sheep-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops-backend': minor +--- + +Fixed bug in plugin-azure-devops-backend where proper error was not thrown when gitRepository was not found. diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts index 174c5ff6cd..a056beee02 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts @@ -465,6 +465,43 @@ describe('AzureDevOpsApi', () => { ]); }); + it('should throw error when gitRepository is undefined', async () => { + const mockApi = { + getGitApi: jest.fn().mockReturnValue({}), + serverUrl: 'serverUrl', + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, + }); + + const pullRequestOptions: PullRequestOptions = { + top: 10, + status: PullRequestStatus.Active, + }; + + api.getGitRepository = jest.fn().mockResolvedValue(undefined); + + const temp = async () => { + try { + await api.getPullRequests('project', 'repo', pullRequestOptions); + return null; + } catch (error) { + return error; + } + }; + + const error = await temp(); + + expect(error).toHaveProperty( + 'message', + 'No repository found for Project "project" with Repository "repo" on host "undefined" under organization "undefined".', + ); + }); + it('should get build definitions', async () => { const mockBuilds: Build[] = [ { diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index 4ed54e63eb..468d6a9d88 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -231,6 +231,11 @@ export class AzureDevOpsApi { host, org, ); + if (!gitRepository) { + throw new Error( + `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, + ); + } const buildList = await this.getBuildList( projectName, gitRepository.id as string, @@ -262,6 +267,11 @@ export class AzureDevOpsApi { host, org, ); + if (!gitRepository) { + throw new Error( + `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, + ); + } const webApi = await this.getWebApi(host, org); const client = await webApi.getGitApi(); const tagRefs: GitRef[] = await client.getRefs( @@ -304,6 +314,11 @@ export class AzureDevOpsApi { host, org, ); + if (!gitRepository) { + throw new Error( + `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, + ); + } const webApi = await this.getWebApi(host, org); const client = await webApi.getGitApi(); const searchCriteria: GitPullRequestSearchCriteria = { @@ -514,6 +529,11 @@ export class AzureDevOpsApi { host, org, ); + if (!gitRepository) { + throw new Error( + `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, + ); + } repoId = gitRepository.id; } From 32da2b1954e360ffb083cbff3d6669878a2aaba9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 02:26:13 +0000 Subject: [PATCH 054/567] chore(deps): update dependency @types/tar to v6.1.13 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ad5d48bd8f..4b093fd7f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19770,12 +19770,12 @@ __metadata: linkType: hard "@types/tar@npm:^6.1.1": - version: 6.1.12 - resolution: "@types/tar@npm:6.1.12" + version: 6.1.13 + resolution: "@types/tar@npm:6.1.13" dependencies: "@types/node": "*" minipass: ^4.0.0 - checksum: b1cbae1894cc943e3a86f88613853986f97f552c6bec34ee990a47fe5905871d1552397ff440108233d75d05653be2fbc356e62beb0b93a45b927fb88060b438 + checksum: bb3910936a6b37f093e38b73a52b0544fd73079685f5ea72e5c049fddc3770e58d80cf6d714425853f0746290221852c1a7ca89ffdb9614f3b2a858a3bf5436a languageName: node linkType: hard From bdf5d3c8e726ea886a27d039147fe1c943173905 Mon Sep 17 00:00:00 2001 From: Ishwarya Surendrababu Date: Wed, 17 Apr 2024 09:15:11 +0530 Subject: [PATCH 055/567] updating docs link Signed-off-by: Ishwarya Surendrababu --- microsite/data/plugins/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/deploy.yaml b/microsite/data/plugins/deploy.yaml index 1e2394a920..a619a7ba0b 100644 --- a/microsite/data/plugins/deploy.yaml +++ b/microsite/data/plugins/deploy.yaml @@ -4,7 +4,7 @@ author: digital.ai authorUrl: https://digital.ai/ category: CI/CD description: The plugin offers integration with Digital.ai Deploy and backstage components and services. It provide access to deployments and reports. -documentation: https://github.com/digital-ai/backstage-deploy/blob/main/README.md +documentation: https://docs.digital.ai/bundle/devops-deploy-version-v.24.1/page/deploy/concept/xl-deploy-backstage-overview.html iconUrl: /img/deploy.svg npmPackageName: '@digital.ai/plugin-dai-deploy' tags: From dbde8c0d6fa8d5937faad7bb347c4ab5440143b0 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Fri, 19 Apr 2024 09:46:44 -0300 Subject: [PATCH 056/567] feat: EntityListComponent now uses Catalog Presentation API Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- plugins/catalog-import/package.json | 1 + .../EntityListComponent.tsx | 43 ++++-- .../StepPrepareSelectLocations.test.tsx | 126 ++++++++++++------ 3 files changed, 118 insertions(+), 52 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 67215f6a74..8a882206a3 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -78,6 +78,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/plugin-catalog": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", diff --git a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx index ef57fad204..9c4af562e5 100644 --- a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx +++ b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx @@ -14,11 +14,15 @@ * limitations under the License. */ -import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; -import { useApp } from '@backstage/core-plugin-api'; +import { + Entity, + CompoundEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { useApi, useApp } from '@backstage/core-plugin-api'; import { EntityRefLink, - humanizeEntityRef, + entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; import Collapse from '@material-ui/core/Collapse'; import IconButton from '@material-ui/core/IconButton'; @@ -38,12 +42,6 @@ const useStyles = makeStyles(theme => ({ }, })); -function sortEntities(entities: Array) { - return entities.sort((a, b) => - humanizeEntityRef(a).localeCompare(humanizeEntityRef(b)), - ); -} - /** * Props for {@link EntityListComponent}. * @@ -78,7 +76,7 @@ export const EntityListComponent = (props: EntityListComponentProps) => { const app = useApp(); const classes = useStyles(); - + const entityPresentationApi = useApi(entityPresentationApiRef); const [expandedUrls, setExpandedUrls] = useState([]); const handleClick = (url: string) => { @@ -87,6 +85,17 @@ export const EntityListComponent = (props: EntityListComponentProps) => { ); }; + function sortEntities(entities: Array) { + return entities.sort((a, b) => + entityPresentationApi + .forEntity(stringifyEntityRef(a)) + .snapshot.entityRef.localeCompare( + entityPresentationApi.forEntity(stringifyEntityRef(b)).snapshot + .entityRef, + ), + ); + } + return ( {firstListItem} @@ -129,7 +138,11 @@ export const EntityListComponent = (props: EntityListComponentProps) => { ); return ( { : {})} > {Icon && } - + ); })} diff --git a/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.test.tsx b/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.test.tsx index b5efd3fbbf..e5a4174859 100644 --- a/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.test.tsx @@ -14,14 +14,22 @@ * limitations under the License. */ -import { renderInTestApp } from '@backstage/test-utils'; +import { TestApiProvider, renderInTestApp } from '@backstage/test-utils'; import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { AnalyzeResult } from '../../api'; import { StepPrepareSelectLocations } from './StepPrepareSelectLocations'; +import { + CatalogApi, + catalogApiRef, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; +import { Entity } from '@backstage/catalog-model'; describe('', () => { + let entities: Entity[]; const analyzeResult = { type: 'locations', locations: [ @@ -53,17 +61,43 @@ describe('', () => { ], } as Extract; + const catalogApi: jest.Mocked = { + getLocationById: jest.fn(), + getEntityByName: jest.fn(), + getEntities: jest.fn(async () => ({ items: entities })), + addLocation: jest.fn(), + getLocationByRef: jest.fn(), + removeEntityByUid: jest.fn(), + } as any; + let Wrapper: React.ComponentType>; + beforeEach(() => { jest.resetAllMocks(); + catalogApi.getEntities.mockResolvedValue({ items: entities }); + Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + + ); }); it('renders display locations to be added', async () => { await renderInTestApp( - undefined} - onGoBack={() => undefined} - />, + + undefined} + onGoBack={() => undefined} + /> + , ); expect(screen.getByText('url-1')).toBeInTheDocument(); @@ -96,11 +130,13 @@ describe('', () => { } as Extract; await renderInTestApp( - undefined} - onGoBack={() => undefined} - />, + + undefined} + onGoBack={() => undefined} + /> + , ); expect(screen.getByText(/my-target/)).toBeInTheDocument(); @@ -112,11 +148,13 @@ describe('', () => { it('should select and deselect all', async () => { await renderInTestApp( - undefined} - onGoBack={() => undefined} - />, + + undefined} + onGoBack={() => undefined} + /> + , ); const checkboxes = screen.getAllByRole('checkbox'); @@ -144,15 +182,17 @@ describe('', () => { it('should preselect prepared locations', async () => { await renderInTestApp( - undefined} - onGoBack={() => undefined} - />, + + undefined} + onGoBack={() => undefined} + /> + , ); const checkboxes = screen.getAllByRole('checkbox'); @@ -164,11 +204,13 @@ describe('', () => { it('should select items', async () => { await renderInTestApp( - undefined} - onGoBack={() => undefined} - />, + + undefined} + onGoBack={() => undefined} + /> + , ); const checkboxes = screen.getAllByRole('checkbox'); @@ -193,11 +235,13 @@ describe('', () => { const onGoBack = jest.fn(); await renderInTestApp( - undefined} - onGoBack={onGoBack} - />, + + undefined} + onGoBack={onGoBack} + /> + , ); await act(async () => { @@ -211,11 +255,13 @@ describe('', () => { const onPrepare = jest.fn(); await renderInTestApp( - undefined} - />, + + undefined} + /> + , ); const checkboxes = screen.getAllByRole('checkbox'); From 026c199ead6734043e805fecb62792197d9b152d Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Fri, 19 Apr 2024 20:30:15 +0530 Subject: [PATCH 057/567] removing react-text-truncate Signed-off-by: npiyush97 --- .changeset/strange-doors-glow.md | 5 +++ .../OverflowTooltip/OverflowTooltip.tsx | 34 +++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 .changeset/strange-doors-glow.md diff --git a/.changeset/strange-doors-glow.md b/.changeset/strange-doors-glow.md new file mode 100644 index 0000000000..71dcc375c0 --- /dev/null +++ b/.changeset/strange-doors-glow.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Removing react-text-truncate with css styles. diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index da489725e4..8585da1013 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ /* * Copyright 2020 The Backstage Authors * @@ -19,6 +20,7 @@ import Tooltip, { TooltipProps } from '@material-ui/core/Tooltip'; import React, { useState } from 'react'; import TextTruncate, { TextTruncateProps } from 'react-text-truncate'; import { useIsMounted } from '@react-hookz/web'; +import Typography from '@material-ui/core/Typography'; type Props = { text: TextTruncateProps['text']; @@ -35,33 +37,35 @@ const useStyles = makeStyles( container: { overflow: 'visible !important', }, + typo: { + // width: 200, + display: 'inline-block', + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + }, }, { name: 'BackstageOverflowTooltip' }, ); export function OverflowTooltip(props: Props) { - const [hover, setHover] = useState(false); - const isMounted = useIsMounted(); + // const [hover, setHover] = useState(false); + // const isMounted = useIsMounted(); const classes = useStyles(); - - const handleToggled = (truncated: boolean) => { - if (isMounted()) { - setHover(truncated); - } - }; + console.log(classes); + // const handleToggled = (truncated: boolean) => { + // if (isMounted()) { + // setHover(truncated); + // } + // }; return ( - + {props.text} ); } From ad0ee563836559f34963f2ac7639aca1cf1679ad Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Fri, 19 Apr 2024 20:56:19 +0530 Subject: [PATCH 058/567] little cleanup Signed-off-by: npiyush97 --- .../OverflowTooltip/OverflowTooltip.tsx | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index 8585da1013..b7a33f97fc 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -17,15 +17,11 @@ import { makeStyles } from '@material-ui/core/styles'; import Tooltip, { TooltipProps } from '@material-ui/core/Tooltip'; -import React, { useState } from 'react'; -import TextTruncate, { TextTruncateProps } from 'react-text-truncate'; -import { useIsMounted } from '@react-hookz/web'; +import React from 'react'; import Typography from '@material-ui/core/Typography'; type Props = { - text: TextTruncateProps['text']; - line?: TextTruncateProps['line']; - element?: TextTruncateProps['element']; + text: string; title?: TooltipProps['title']; placement?: TooltipProps['placement']; }; @@ -38,7 +34,6 @@ const useStyles = makeStyles( overflow: 'visible !important', }, typo: { - // width: 200, display: 'inline-block', overflow: 'hidden', whiteSpace: 'nowrap', @@ -49,21 +44,12 @@ const useStyles = makeStyles( ); export function OverflowTooltip(props: Props) { - // const [hover, setHover] = useState(false); - // const isMounted = useIsMounted(); const classes = useStyles(); - console.log(classes); - // const handleToggled = (truncated: boolean) => { - // if (isMounted()) { - // setHover(truncated); - // } - // }; return ( {props.text} From 7c234d0cead00265f05cbc81a6a84b5bd07d1b5f Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Fri, 19 Apr 2024 21:25:00 +0530 Subject: [PATCH 059/567] added style maxwidth 200 and props Signed-off-by: npiyush97 --- packages/core-components/api-report.md | 1 - packages/core-components/package.json | 46 +++++++++---------- .../OverflowTooltip.stories.tsx | 8 +--- .../OverflowTooltip/OverflowTooltip.tsx | 3 +- 4 files changed, 26 insertions(+), 32 deletions(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 7fd706db43..06676ab429 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -47,7 +47,6 @@ import { StyledComponentProps } from '@material-ui/core/styles/withStyles'; import { StyleRules } from '@material-ui/styles'; import { StyleRules as StyleRules_2 } from '@material-ui/core/styles/withStyles'; import { TabProps } from '@material-ui/core/Tab'; -import { TextTruncateProps } from 'react-text-truncate'; import { Theme } from '@material-ui/core/styles'; import { TooltipProps } from '@material-ui/core/Tooltip'; import { WithStyles } from '@material-ui/core/styles'; diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 9f632bcc7f..cbdb256c65 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,31 +1,32 @@ { "name": "@backstage/core-components", - "description": "Core components used by Backstage plugins and apps", "version": "0.14.4", - "publishConfig": { - "access": "public" - }, + "description": "Core components used by Backstage plugins and apps", "backstage": { "role": "web-library" }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/core-components" }, - "keywords": [ - "backstage" - ], "license": "Apache-2.0", - "main": "src/index.ts", - "types": "src/index.ts", + "sideEffects": false, "exports": { ".": "./src/index.ts", "./alpha": "./src/alpha.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, + "main": "src/index.ts", + "types": "src/index.ts", "typesVersions": { "*": { "alpha": [ @@ -39,15 +40,18 @@ ] } }, - "sideEffects": false, + "files": [ + "dist", + "config.d.ts" + ], "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/config": "workspace:^", @@ -63,7 +67,6 @@ "@react-hookz/web": "^24.0.0", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", "@types/react-sparklines": "^1.7.0", - "@types/react-text-truncate": "^0.14.0", "ansi-regex": "^6.0.1", "classnames": "^2.2.6", "d3-selection": "^3.0.0", @@ -82,7 +85,6 @@ "react-markdown": "^8.0.0", "react-sparklines": "^1.7.0", "react-syntax-highlighter": "^15.4.5", - "react-text-truncate": "^0.19.0", "react-use": "^17.3.2", "react-virtualized-auto-sizer": "^1.0.11", "react-window": "^1.8.6", @@ -90,11 +92,6 @@ "zen-observable": "^0.10.0", "zod": "^3.22.4" }, - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", - "react-router-dom": "6.0.0-beta.0 || ^6.3.0" - }, "devDependencies": { "@backstage/app-defaults": "workspace:^", "@backstage/cli": "workspace:^", @@ -120,9 +117,10 @@ "history": "^5.0.0", "msw": "^1.0.0" }, - "files": [ - "dist", - "config.d.ts" - ], + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" + }, "configSchema": "config.d.ts" } diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx index 5ab5ba7dc4..a302a0a7e3 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx @@ -33,16 +33,12 @@ export const Default = () => ( export const MultiLine = () => ( - + ); export const DifferentTitle = () => ( - + ); diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index b7a33f97fc..12de79644f 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -21,7 +21,7 @@ import React from 'react'; import Typography from '@material-ui/core/Typography'; type Props = { - text: string; + text?: string | undefined; title?: TooltipProps['title']; placement?: TooltipProps['placement']; }; @@ -34,6 +34,7 @@ const useStyles = makeStyles( overflow: 'visible !important', }, typo: { + maxWidth: 200, display: 'inline-block', overflow: 'hidden', whiteSpace: 'nowrap', From 3e39b436e5a1cf837335acb57f6d7811e7683ba3 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Fri, 19 Apr 2024 21:26:54 +0530 Subject: [PATCH 060/567] add yarn.lock Signed-off-by: npiyush97 --- yarn.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0a27eecdea..8ef4c2bab1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3923,7 +3923,6 @@ __metadata: "@types/react-helmet": ^6.1.0 "@types/react-sparklines": ^1.7.0 "@types/react-syntax-highlighter": ^15.0.0 - "@types/react-text-truncate": ^0.14.0 "@types/react-virtualized-auto-sizer": ^1.0.1 "@types/react-window": ^1.8.5 "@types/zen-observable": ^0.8.0 @@ -3948,7 +3947,6 @@ __metadata: react-markdown: ^8.0.0 react-sparklines: ^1.7.0 react-syntax-highlighter: ^15.4.5 - react-text-truncate: ^0.19.0 react-use: ^17.3.2 react-virtualized-auto-sizer: ^1.0.11 react-window: ^1.8.6 From d74a191b6c269ae9bcd99769d6d2a1d5dc902ff4 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Fri, 19 Apr 2024 21:33:31 +0530 Subject: [PATCH 061/567] missing this one Signed-off-by: npiyush97 --- plugins/catalog-react/src/components/EntityTable/columns.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 292235c78c..3357f1dded 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -141,7 +141,6 @@ export const columnFactories = Object.freeze({ ), }; From ff03fd55dee6b85b63ee79bdd8a1930433b46f8a Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sat, 20 Apr 2024 12:03:21 -0300 Subject: [PATCH 062/567] feat: allow promise in EntityPresentationApi for all the asynchronous processes working under the hood Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../EntityPresentationApi.ts | 5 ++++ .../DefaultEntityPresentationApi.ts | 29 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts index 945bce1d27..de5449917a 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts @@ -109,6 +109,11 @@ export interface EntityRefPresentation { * elsewhere. */ update$?: Observable; + + /* The `promise` property in the `EntityRefPresentation` interface is defining a property named + `promise` that holds a promise. This promise resolves to an array of + `EntityRefPresentationSnapshot` objects. */ + promise: Promise; } /** diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts index 82862be435..bbd5540d15 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts @@ -298,10 +298,37 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { }; }); - return { + const entityRefPresentation: EntityRefPresentation = { snapshot: initialSnapshot, update$: observable, + get promise() { + return new Promise( + (resolve, reject) => { + if (!observable) { + resolve([initialSnapshot]); + } else { + const res: EntityRefPresentationSnapshot[] = []; + const subscription = observable.subscribe({ + next: snapshot => { + res.push(snapshot); + }, + error: error => { + initialSnapshot = { + primaryTitle: entityRef, + entityRef: entityRef, + }; + }, + complete() { + subscription.unsubscribe(); + resolve(res); + }, + }); + } + }, + ); + }, }; + return entityRefPresentation; } #getEntityForInitialRender(entityOrRef: Entity | string): { From 929cb2611873cee3d562530e0313b2ca9c727aa3 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sat, 20 Apr 2024 12:09:43 -0300 Subject: [PATCH 063/567] feat: MultiEntityPicker uses entityPresentationApi to display entity instead of humanizeEntityRef Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../MultiEntityPicker.test.tsx | 17 ++++- .../MultiEntityPicker/MultiEntityPicker.tsx | 76 +++++++++++-------- 2 files changed, 60 insertions(+), 33 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx index bc0a5ed32d..4be07b139d 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx @@ -16,7 +16,11 @@ import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + CatalogApi, + catalogApiRef, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { fireEvent, screen } from '@testing-library/react'; @@ -24,6 +28,7 @@ import React from 'react'; import { MultiEntityPicker } from './MultiEntityPicker'; import { MultiEntityPickerProps } from './schema'; import { ScaffolderRJSFFieldProps as FieldProps } from '@backstage/plugin-scaffolder-react'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -59,7 +64,15 @@ describe('', () => { ]; Wrapper = ({ children }: { children?: React.ReactNode }) => ( - + {children} ); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index d9804a8cba..0a0fcfdc2b 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -25,7 +25,9 @@ import { import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, - humanizeEntityRef, + entityPresentationApiRef, + EntityRefPresentationSnapshot, + EntityDisplayName, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; @@ -64,40 +66,35 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { uiSchema['ui:options']?.defaultNamespace || undefined; const catalogApi = useApi(catalogApiRef); - + const entityPresentationApi = useApi(entityPresentationApiRef); const { value: entities, loading } = useAsync(async () => { const { items } = await catalogApi.getEntities( catalogFilter ? { filter: catalogFilter } : undefined, ); - return items; + const primaryTitles: string[] = []; + for (const item of items) { + const entityPresentation = (await entityPresentationApi.forEntity(item) + ?.promise) as EntityRefPresentationSnapshot[]; + entityPresentation.map(e => primaryTitles.push(e.primaryTitle)); + } + + return { items, primaryTitles }; }); const allowArbitraryValues = uiSchema['ui:options']?.allowArbitraryValues ?? true; - const getLabel = useCallback( - (ref: string) => { - try { - return humanizeEntityRef( - parseEntityRef(ref, { defaultKind, defaultNamespace }), - { - defaultKind, - defaultNamespace, - }, - ); - } catch (err) { - return ref; - } - }, - [defaultKind, defaultNamespace], - ); - const onSelect = useCallback( (_: any, refs: (string | Entity)[], reason: AutocompleteChangeReason) => { const values = refs .map(ref => { if (typeof ref !== 'string') { // if ref does not exist: pass 'undefined' to trigger validation for required value - return ref ? stringifyEntityRef(ref as Entity) : undefined; + return ref + ? entityPresentationApi.forEntity(ref, { + defaultKind, + defaultNamespace, + }).snapshot.entityRef + : undefined; } if (reason === 'blur' || reason === 'create-option') { // Add in default namespace, etc. @@ -126,14 +123,21 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { onChange(values); }, - [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues], + [ + onChange, + formData, + defaultKind, + defaultNamespace, + allowArbitraryValues, + entityPresentationApi, + ], ); useEffect(() => { - if (entities?.length === 1) { - onChange([stringifyEntityRef(entities[0])]); + if (entities?.items?.length === 1) { + onChange([stringifyEntityRef(entities.items[0])]); } - }, [entities, onChange]); + }, [entities?.items, onChange]); return ( { formData && formData.includes(stringifyEntityRef(e)), - ) ?? (allowArbitraryValues && formData ? formData.map(getLabel) : []) + ) ?? + (allowArbitraryValues && formData + ? entities?.primaryTitles || [] + : []) } loading={loading} onChange={onSelect} - options={entities || []} + options={entities?.items || []} + renderOption={option => } getOptionLabel={option => // option can be a string due to freeSolo. typeof option === 'string' ? option - : humanizeEntityRef(option, { defaultKind, defaultNamespace })! + : entityPresentationApi.forEntity(option, { + defaultKind, + defaultNamespace, + }).snapshot.entityRef! } autoSelect freeSolo={allowArbitraryValues} @@ -170,7 +181,10 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { label={title} margin="dense" helperText={description} - FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} + FormHelperTextProps={{ + margin: 'dense', + style: { marginLeft: 0 }, + }} variant="outlined" required={required} InputProps={params.InputProps} From 82214e72c9a8abc6c39613841fc48f30210e39eb Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sat, 20 Apr 2024 14:18:23 -0300 Subject: [PATCH 064/567] Revert "bugfix: Proper error thrown in plugin-azure-devops-backend when gitRepository is not found." This reverts commit 88191bb54ebf9e118f3c5fbbd2a7ab7b1e669bd6. Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .changeset/three-sheep-remember.md | 5 --- .../src/api/AzureDevOpsApi.test.ts | 37 ------------------- .../src/api/AzureDevOpsApi.ts | 20 ---------- 3 files changed, 62 deletions(-) delete mode 100644 .changeset/three-sheep-remember.md diff --git a/.changeset/three-sheep-remember.md b/.changeset/three-sheep-remember.md deleted file mode 100644 index 51ffc8e3db..0000000000 --- a/.changeset/three-sheep-remember.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-azure-devops-backend': minor ---- - -Fixed bug in plugin-azure-devops-backend where proper error was not thrown when gitRepository was not found. diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts index a056beee02..174c5ff6cd 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts @@ -465,43 +465,6 @@ describe('AzureDevOpsApi', () => { ]); }); - it('should throw error when gitRepository is undefined', async () => { - const mockApi = { - getGitApi: jest.fn().mockReturnValue({}), - serverUrl: 'serverUrl', - }; - - (WebApi as unknown as jest.Mock).mockImplementation(() => mockApi); - - const api = AzureDevOpsApi.fromConfig(mockConfig, { - logger: mockLogger, - urlReader: mockUrlReader, - }); - - const pullRequestOptions: PullRequestOptions = { - top: 10, - status: PullRequestStatus.Active, - }; - - api.getGitRepository = jest.fn().mockResolvedValue(undefined); - - const temp = async () => { - try { - await api.getPullRequests('project', 'repo', pullRequestOptions); - return null; - } catch (error) { - return error; - } - }; - - const error = await temp(); - - expect(error).toHaveProperty( - 'message', - 'No repository found for Project "project" with Repository "repo" on host "undefined" under organization "undefined".', - ); - }); - it('should get build definitions', async () => { const mockBuilds: Build[] = [ { diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index 468d6a9d88..4ed54e63eb 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -231,11 +231,6 @@ export class AzureDevOpsApi { host, org, ); - if (!gitRepository) { - throw new Error( - `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, - ); - } const buildList = await this.getBuildList( projectName, gitRepository.id as string, @@ -267,11 +262,6 @@ export class AzureDevOpsApi { host, org, ); - if (!gitRepository) { - throw new Error( - `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, - ); - } const webApi = await this.getWebApi(host, org); const client = await webApi.getGitApi(); const tagRefs: GitRef[] = await client.getRefs( @@ -314,11 +304,6 @@ export class AzureDevOpsApi { host, org, ); - if (!gitRepository) { - throw new Error( - `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, - ); - } const webApi = await this.getWebApi(host, org); const client = await webApi.getGitApi(); const searchCriteria: GitPullRequestSearchCriteria = { @@ -529,11 +514,6 @@ export class AzureDevOpsApi { host, org, ); - if (!gitRepository) { - throw new Error( - `No repository found for Project "${projectName}" with Repository "${repoName}" on host "${host}" under organization "${org}".`, - ); - } repoId = gitRepository.id; } From e1174b01b20e4a29fc81b07f25798aebb2595729 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sat, 20 Apr 2024 15:28:46 -0300 Subject: [PATCH 065/567] chore: changeset added for this feature Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .changeset/five-cows-crash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/five-cows-crash.md diff --git a/.changeset/five-cows-crash.md b/.changeset/five-cows-crash.md new file mode 100644 index 0000000000..2c298dbad3 --- /dev/null +++ b/.changeset/five-cows-crash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +`EntityListComponent` uses `entityPresentationApi` instead of `humanizeEntityRef` to display Entity From 4946ed88513e70ed5787c4503096810097f994c3 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sat, 20 Apr 2024 15:45:50 -0300 Subject: [PATCH 066/567] chore: yarn.lock updated with new dependencies Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index 0a27eecdea..5b5aef8874 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5868,6 +5868,7 @@ __metadata: "@backstage/frontend-plugin-api": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" + "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" From 821f902bfec625628ce82e38003a37c0bc9cd78f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 20 Apr 2024 22:15:57 -0400 Subject: [PATCH 067/567] update to diff instead of check Signed-off-by: aramissennyeydd --- .changeset/flat-countries-clap.md | 2 +- packages/repo-tools/src/commands/index.ts | 14 +++++++------- .../package/schema/openapi/{check.ts => diff.ts} | 0 .../repo/schema/openapi/{check.ts => diff.ts} | 12 ++++-------- plugins/catalog-backend/package.json | 2 +- 5 files changed, 13 insertions(+), 17 deletions(-) rename packages/repo-tools/src/commands/package/schema/openapi/{check.ts => diff.ts} (100%) rename packages/repo-tools/src/commands/repo/schema/openapi/{check.ts => diff.ts} (90%) diff --git a/.changeset/flat-countries-clap.md b/.changeset/flat-countries-clap.md index ccd779a2b5..9c962b04a3 100644 --- a/.changeset/flat-countries-clap.md +++ b/.changeset/flat-countries-clap.md @@ -2,4 +2,4 @@ '@backstage/repo-tools': minor --- -Adds 2 new commands `repo schema openapi check` and `package schema openapi check`. `repo schema openapi check` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes.They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. +Adds 2 new commands `repo schema openapi diff` and `package schema openapi diff`. `repo schema openapi diff` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes. They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 3fa16aa512..83266ea9f9 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -80,12 +80,12 @@ function registerPackageCommand(program: Command) { ); openApiCommand - .command('check') + .command('diff') .option('--ignore', 'Ignore linting failures and only log the results.') .option('--json', 'Output the results as JSON') - .option('--since ', 'Check the API against a specific ref') + .option('--since ', 'Diff the API against a specific ref') .action( - lazy(() => import('./package/schema/openapi/check').then(m => m.command)), + lazy(() => import('./package/schema/openapi/diff').then(m => m.command)), ); } @@ -144,17 +144,17 @@ function registerRepoCommand(program: Command) { ); openApiCommand - .command('check') + .command('diff') .description( - 'Check the repository against a specific ref, will run all package `check:api` scripts.', + 'Diff the repository against a specific ref, will run all package `diff` scripts.', ) .option( '--since ', - 'Check the API against a specific ref', + 'Diff the API against a specific ref', 'origin/master', ) .action( - lazy(() => import('./repo/schema/openapi/check').then(m => m.command)), + lazy(() => import('./repo/schema/openapi/diff').then(m => m.command)), ); } diff --git a/packages/repo-tools/src/commands/package/schema/openapi/check.ts b/packages/repo-tools/src/commands/package/schema/openapi/diff.ts similarity index 100% rename from packages/repo-tools/src/commands/package/schema/openapi/check.ts rename to packages/repo-tools/src/commands/package/schema/openapi/diff.ts diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts similarity index 90% rename from packages/repo-tools/src/commands/repo/schema/openapi/check.ts rename to packages/repo-tools/src/commands/repo/schema/openapi/diff.ts index 95bd4dffed..02f3db0c43 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts @@ -54,9 +54,7 @@ export async function command(opts: OptionValues) { ); } - const checkablePackages = packages.filter( - e => e.packageJson.scripts?.['check:api'], - ); + const checkablePackages = packages.filter(e => e.packageJson.scripts?.diff); try { const outputs = { @@ -70,7 +68,7 @@ export async function command(opts: OptionValues) { const sinceCommands = since ? ['--since', since] : []; const { stdout } = await exec( 'yarn', - ['check:api', '--ignore', '--json', ...sinceCommands], + ['diff', '--ignore', '--json', ...sinceCommands], { cwd: pkg.dir, }, @@ -81,12 +79,10 @@ export async function command(opts: OptionValues) { outputs.noop.push(...(result.noop ?? [])); } - for (const pkg of packages.filter( - e => !e.packageJson.scripts?.['check:api'], - )) { + for (const pkg of packages.filter(e => !e.packageJson.scripts?.diff)) { outputs.warning?.push({ apiName: `${pkg.dir}/`, - warning: 'No check:api script found in package.json', + warning: 'No diff script found in package.json', }); } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 058b9318d7..04a7778926 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -42,8 +42,8 @@ ], "scripts": { "build": "backstage-cli package build", - "check:api": "backstage-repo-tools package schema openapi check", "clean": "backstage-cli package clean", + "diff": "backstage-repo-tools package schema openapi diff", "fuzz": "backstage-repo-tools package schema openapi fuzz --exclude-checks response_schema_conformance", "generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/catalog-client", "lint": "backstage-cli package lint", From 4268696352ec81671b543059ce732b1442b6ee92 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sun, 21 Apr 2024 21:57:36 -0300 Subject: [PATCH 068/567] chore: changeset added Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .changeset/olive-rockets-drum.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/olive-rockets-drum.md diff --git a/.changeset/olive-rockets-drum.md b/.changeset/olive-rockets-drum.md new file mode 100644 index 0000000000..12f80dab5c --- /dev/null +++ b/.changeset/olive-rockets-drum.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-scaffolder': minor +'@backstage/plugin-catalog': minor +--- + +`MultiEntityPicker` uses `entityPresentationApi` instead of `humanizeEntityRef` to display entity. Also, `EntityPresentationApi` now allows `promise` getter for under asynchronous process of presentation api From f4856e9f9b1d950565a32e431a4c835a82354e91 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 22 Apr 2024 09:26:20 -0400 Subject: [PATCH 069/567] add api report Signed-off-by: aramissennyeydd --- packages/repo-tools/cli-report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 68d679fb06..a06f426baf 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -172,14 +172,14 @@ Commands: lint [options] [paths...] test [options] [paths...] fuzz [options] - check [options] + diff [options] help [command] ``` -### `backstage-repo-tools repo schema openapi check` +### `backstage-repo-tools repo schema openapi diff` ``` -Usage: backstage-repo-tools repo schema openapi check [options] +Usage: backstage-repo-tools repo schema openapi diff [options] Options: --since From 5dc5f4f8ba7a7e7d4b1891ed9c2d9c7e6e131322 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 3 Apr 2024 22:16:06 -0400 Subject: [PATCH 070/567] refactor(collators): allow tokenManager param to be optional Signed-off-by: Phil Kuang --- .changeset/wild-seahorses-grin.md | 6 ++++++ plugins/search-backend-module-catalog/api-report.md | 2 +- .../src/collators/DefaultCatalogCollatorFactory.ts | 4 +--- plugins/search-backend-module-techdocs/api-report.md | 2 +- .../src/collators/DefaultTechDocsCollatorFactory.ts | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 .changeset/wild-seahorses-grin.md diff --git a/.changeset/wild-seahorses-grin.md b/.changeset/wild-seahorses-grin.md new file mode 100644 index 0000000000..94b30a6839 --- /dev/null +++ b/.changeset/wild-seahorses-grin.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-module-techdocs': patch +'@backstage/plugin-search-backend-module-catalog': patch +--- + +Allow the `tokenManager` parameter to be optional when instantiating collator diff --git a/plugins/search-backend-module-catalog/api-report.md b/plugins/search-backend-module-catalog/api-report.md index fc19ede2d4..8019ab0aac 100644 --- a/plugins/search-backend-module-catalog/api-report.md +++ b/plugins/search-backend-module-catalog/api-report.md @@ -44,7 +44,7 @@ export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory { export type DefaultCatalogCollatorFactoryOptions = { auth?: AuthService; discovery: PluginEndpointDiscovery; - tokenManager: TokenManager; + tokenManager?: TokenManager; locationTemplate?: string; filter?: GetEntitiesRequest['filter']; batchSize?: number; diff --git a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts index 166985f2d5..22dcc67233 100644 --- a/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts +++ b/plugins/search-backend-module-catalog/src/collators/DefaultCatalogCollatorFactory.ts @@ -40,7 +40,7 @@ import { AuthService } from '@backstage/backend-plugin-api'; export type DefaultCatalogCollatorFactoryOptions = { auth?: AuthService; discovery: PluginEndpointDiscovery; - tokenManager: TokenManager; + tokenManager?: TokenManager; /** * @deprecated Use the config key `search.collators.catalog.locationTemplate` instead. */ @@ -95,7 +95,6 @@ export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory { entityTransformer: options.entityTransformer, auth: adaptedAuth, discovery: options.discovery, - tokenManager: options.tokenManager, catalogClient: options.catalogClient, }); } @@ -107,7 +106,6 @@ export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory { entityTransformer?: CatalogCollatorEntityTransformer; auth: AuthService; discovery: PluginEndpointDiscovery; - tokenManager: TokenManager; catalogClient?: CatalogApi; }) { const { diff --git a/plugins/search-backend-module-techdocs/api-report.md b/plugins/search-backend-module-techdocs/api-report.md index fbdecef2a8..35cf8533cf 100644 --- a/plugins/search-backend-module-techdocs/api-report.md +++ b/plugins/search-backend-module-techdocs/api-report.md @@ -45,7 +45,7 @@ export type TechDocsCollatorEntityTransformer = ( export type TechDocsCollatorFactoryOptions = { discovery: PluginEndpointDiscovery; logger: LoggerService; - tokenManager: TokenManager; + tokenManager?: TokenManager; auth?: AuthService; httpAuth?: HttpAuthService; locationTemplate?: string; diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts index ad820e6ad2..10c09d02b2 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.ts @@ -61,7 +61,7 @@ interface MkSearchIndexDoc { export type TechDocsCollatorFactoryOptions = { discovery: PluginEndpointDiscovery; logger: LoggerService; - tokenManager: TokenManager; + tokenManager?: TokenManager; auth?: AuthService; httpAuth?: HttpAuthService; locationTemplate?: string; From ac6cff672e9dd1e8194a342610e77f4a2d3eeec1 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Mon, 22 Apr 2024 22:23:01 -0300 Subject: [PATCH 071/567] testcase updated for new promise getter in api Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../DefaultEntityPresentationApi.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts index 42ff777cbd..475bfe1660 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts @@ -35,6 +35,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: new Promise((resolve, reject) => resolve({})), }); expect( @@ -48,6 +49,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: new Promise((resolve, reject) => resolve({})), }); expect( @@ -63,6 +65,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: new Promise((resolve, reject) => resolve({})), }); const entity: Entity = { @@ -85,6 +88,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: new Promise((resolve, reject) => resolve({})), }); }); @@ -150,6 +154,47 @@ describe('DefaultEntityPresentationApi', () => { }), ); }); + + it('returns the correct snapshots via promise', async () => { + const catalogApi = { + getEntitiesByRefs: jest.fn(), + }; + const api = DefaultEntityPresentationApi.create({ + catalogApi: catalogApi as Partial as any, + }); + + catalogApi.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test', + namespace: 'default', + etag: 'something', + }, + spec: { + type: 'service', + }, + }, + ], + }); + + const entityRef = 'component:default/test'; + const entitySnapshot = { + entityRef: entityRef, + primaryTitle: 'test', + secondaryTitle: 'component:default/test | service', + Icon: expect.anything(), + }; + + const promise = api.forEntity(entityRef).promise; + + const snapshots = await promise; + + expect(snapshots.length).toEqual(1); // Only one snapshot expected + expect(snapshots[0]).toEqual(entitySnapshot); // Snapshot should match the simulated one + }); }); async function consumePresentation( From 30e92e77097540746d66d2d3b0330e8cf30119d9 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Mon, 22 Apr 2024 23:18:55 -0300 Subject: [PATCH 072/567] chore: lint errors resolved Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../EntityPresentationApi.ts | 2 +- .../DefaultEntityPresentationApi.test.ts | 12 ++--- .../DefaultEntityPresentationApi.ts | 46 +++++++++---------- 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts index de5449917a..f4f1193718 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts @@ -113,7 +113,7 @@ export interface EntityRefPresentation { /* The `promise` property in the `EntityRefPresentation` interface is defining a property named `promise` that holds a promise. This promise resolves to an array of `EntityRefPresentationSnapshot` objects. */ - promise: Promise; + promise?: Promise; } /** diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts index 475bfe1660..c37efb204f 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts @@ -35,7 +35,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise((resolve, reject) => resolve({})), + promise: new Promise(resolve => resolve({})), }); expect( @@ -49,7 +49,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise((resolve, reject) => resolve({})), + promise: new Promise(resolve => resolve({})), }); expect( @@ -65,7 +65,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise((resolve, reject) => resolve({})), + promise: new Promise(resolve => resolve({})), }); const entity: Entity = { @@ -88,7 +88,7 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise((resolve, reject) => resolve({})), + promise: new Promise(resolve => resolve({})), }); }); @@ -192,8 +192,8 @@ describe('DefaultEntityPresentationApi', () => { const snapshots = await promise; - expect(snapshots.length).toEqual(1); // Only one snapshot expected - expect(snapshots[0]).toEqual(entitySnapshot); // Snapshot should match the simulated one + expect(snapshots?.length).toEqual(1); // Only one snapshot expected + expect(snapshots?.[0]).toEqual(entitySnapshot); // Snapshot should match the simulated one }); }); diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts index bbd5540d15..c7572cca3b 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts @@ -302,30 +302,28 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { snapshot: initialSnapshot, update$: observable, get promise() { - return new Promise( - (resolve, reject) => { - if (!observable) { - resolve([initialSnapshot]); - } else { - const res: EntityRefPresentationSnapshot[] = []; - const subscription = observable.subscribe({ - next: snapshot => { - res.push(snapshot); - }, - error: error => { - initialSnapshot = { - primaryTitle: entityRef, - entityRef: entityRef, - }; - }, - complete() { - subscription.unsubscribe(); - resolve(res); - }, - }); - } - }, - ); + return new Promise(resolve => { + if (!observable) { + resolve([initialSnapshot]); + } else { + const res: EntityRefPresentationSnapshot[] = []; + const subscription = observable.subscribe({ + next: snapshot => { + res.push(snapshot); + }, + error: () => { + initialSnapshot = { + primaryTitle: entityRef, + entityRef: entityRef, + }; + }, + complete() { + subscription.unsubscribe(); + resolve(res); + }, + }); + } + }); }, }; return entityRefPresentation; From a2ee4df20a6884d00328556948c81448d1487dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Fri, 22 Mar 2024 08:46:49 +0100 Subject: [PATCH 073/567] feat: Allow GaugeCard to handle multi-line titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By adding a fullHeightFixedContent variant. Also, add support for a small version. Signed-off-by: Gustaf Räntilä --- .changeset/selfish-walls-visit.md | 5 ++ packages/core-components/api-report.md | 7 +- .../src/components/ProgressBars/Gauge.tsx | 14 +++- .../ProgressBars/GaugeCard.stories.tsx | 76 +++++++++++++++++++ .../src/components/ProgressBars/GaugeCard.tsx | 17 ++++- .../src/layout/InfoCard/InfoCard.tsx | 25 +++++- 6 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 .changeset/selfish-walls-visit.md diff --git a/.changeset/selfish-walls-visit.md b/.changeset/selfish-walls-visit.md new file mode 100644 index 0000000000..8748b3717e --- /dev/null +++ b/.changeset/selfish-walls-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Add a fullHeightFixedContent variant of the GaugeCard, and a small size version. Fixed content will vertically align the gauge in the cards, even when the card titles span across multiple lines. diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 7fd706db43..1e5c1b892f 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -448,6 +448,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + size?: 'normal' | 'small'; description?: ReactNode; getColor?: GaugePropsGetColor; }; @@ -609,7 +610,11 @@ export type InfoCardClassKey = | 'headerContent'; // @public (undocumented) -export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; +export type InfoCardVariants = + | 'flex' + | 'fullHeight' + | 'fullHeightFixedContent' + | 'gridItem'; // Warning: (ae-forgotten-export) The symbol "ItemCardProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ItemCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 088e369e62..87d2bf4f58 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -19,6 +19,7 @@ import { makeStyles, useTheme } from '@material-ui/core/styles'; import { Circle } from 'rc-progress'; import React, { ReactNode, useEffect, useState } from 'react'; import Box from '@material-ui/core/Box'; +import classNames from 'classnames'; /** @public */ export type GaugeClassKey = @@ -43,6 +44,9 @@ const useStyles = makeStyles( fontWeight: theme.typography.fontWeightBold, color: theme.palette.textContrast, }, + overlaySmall: { + fontSize: theme.typography.pxToRem(25), + }, description: { fontSize: '100%', top: '50%', @@ -68,6 +72,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + size?: 'normal' | 'small'; description?: ReactNode; getColor?: GaugePropsGetColor; }; @@ -121,7 +126,7 @@ export const getProgressColor: GaugePropsGetColor = ({ export function Gauge(props: GaugeProps) { const [hoverRef, setHoverRef] = useState(null); - const { getColor = getProgressColor } = props; + const { getColor = getProgressColor, size = 'normal' } = props; const classes = useStyles(props); const { palette } = useTheme(); const { value, fractional, inverse, unit, max, description } = { @@ -165,7 +170,12 @@ export function Gauge(props: GaugeProps) { {description && isHovering ? ( {description} ) : ( - + {isNaN(value) ? 'N/A' : `${asActual}${unit}`} )} diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index a0b6faea60..21d023999e 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -175,6 +175,82 @@ export const InfoMessage = () => ( ); +export const AlignedBottom = () => ( + + + + + + + + + + + + + + +); + +export const Small = () => ( + + + + + + + + + + + + + + +); + export const HoverMessage = () => ( diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 3442395380..bbb870141b 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -27,6 +27,7 @@ type Props = { variant?: InfoCardVariants; /** Progress in % specified as decimal, e.g. "0.23" */ progress: number; + size?: 'normal' | 'small'; description?: ReactNode; icon?: ReactNode; inverse?: boolean; @@ -43,6 +44,10 @@ const useStyles = makeStyles( height: '100%', width: 250, }, + rootSmall: { + height: '100%', + width: 160, + }, }, { name: 'BackstageGaugeCard' }, ); @@ -64,6 +69,7 @@ export function GaugeCard(props: Props) { description, icon, variant, + size = 'normal', getColor, } = props; @@ -75,15 +81,22 @@ export function GaugeCard(props: Props) { }; return ( - + - + ); diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index c80e71b609..9fd72e50b6 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -46,6 +46,10 @@ const useStyles = makeStyles( header: { padding: theme.spacing(2, 2, 2, 2.5), }, + headerFixedContent: { + flexGrow: 1, + alignItems: 'flex-start', + }, headerTitle: { fontWeight: theme.typography.fontWeightBold, }, @@ -87,6 +91,11 @@ const VARIANT_STYLES = { flexDirection: 'column', height: '100%', }, + fullHeightFixedContent: { + display: 'flex', + flexDirection: 'column', + height: '100%', + }, gridItem: { display: 'flex', flexDirection: 'column', @@ -102,6 +111,9 @@ const VARIANT_STYLES = { fullHeight: { flex: 1, }, + fullHeightFixedContent: { + flex: '0 1 0%', + }, gridItem: { flex: 1, }, @@ -109,7 +121,11 @@ const VARIANT_STYLES = { }; /** @public */ -export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; +export type InfoCardVariants = + | 'flex' + | 'fullHeight' + | 'fullHeightFixedContent' + | 'gridItem'; /** * InfoCard is used to display a paper-styled block on the screen, similar to a panel. @@ -228,7 +244,12 @@ export function InfoCard(props: Props): JSX.Element { {title && ( Date: Fri, 22 Mar 2024 16:09:05 +0100 Subject: [PATCH 074/567] Changed to not having a fixed/full sized header, but allow the content to grow, with an option to align the content to the bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .changeset/selfish-walls-visit.md | 3 +- packages/core-components/api-report.md | 6 +--- .../ProgressBars/GaugeCard.stories.tsx | 24 +++++++++----- .../src/components/ProgressBars/GaugeCard.tsx | 3 ++ .../src/layout/InfoCard/InfoCard.tsx | 32 ++++++------------- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/.changeset/selfish-walls-visit.md b/.changeset/selfish-walls-visit.md index 8748b3717e..64c87a64f5 100644 --- a/.changeset/selfish-walls-visit.md +++ b/.changeset/selfish-walls-visit.md @@ -2,4 +2,5 @@ '@backstage/core-components': patch --- -Add a fullHeightFixedContent variant of the GaugeCard, and a small size version. Fixed content will vertically align the gauge in the cards, even when the card titles span across multiple lines. +Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines. +Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card. diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 1e5c1b892f..293695db0c 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -610,11 +610,7 @@ export type InfoCardClassKey = | 'headerContent'; // @public (undocumented) -export type InfoCardVariants = - | 'flex' - | 'fullHeight' - | 'fullHeightFixedContent' - | 'gridItem'; +export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; // Warning: (ae-forgotten-export) The symbol "ItemCardProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ItemCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 21d023999e..12cdef52cb 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -179,7 +179,8 @@ export const AlignedBottom = () => ( ( ( ( ( ( ( ( From 0b9d63a02519628a5a3546376451b6dc3ff33966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Sat, 6 Apr 2024 08:55:14 +0200 Subject: [PATCH 075/567] fix: Minor refactoring of styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .../core-components/src/components/ProgressBars/Gauge.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 87d2bf4f58..e72172fd59 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -171,10 +171,9 @@ export function Gauge(props: GaugeProps) { {description} ) : ( {isNaN(value) ? 'N/A' : `${asActual}${unit}`} From a7648561962ac8d80c1145aa3b4e488e3063d427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Sat, 6 Apr 2024 09:28:13 +0200 Subject: [PATCH 076/567] feat: Added subheaderTypographyProps prop to InfoCard, allowing it to be used from GaugeCard. Also made GaugeCard 'small' variant to have smaller text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .../components/ProgressBars/GaugeCard.stories.tsx | 1 + .../src/components/ProgressBars/GaugeCard.tsx | 13 ++++++------- .../src/layout/InfoCard/InfoCard.tsx | 3 +++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 12cdef52cb..f7e2eb69c6 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -234,6 +234,7 @@ export const Small = () => ( alignGauge="bottom" size="small" title="Progress" + subheader="With a subheader" progress={0.57} /> diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 4e73487983..9f6de8eafe 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -91,13 +91,12 @@ export function GaugeCard(props: Props) { variant={variant} alignContent={alignGauge} icon={icon} - titleTypographyProps={ - size === 'small' - ? { - variant: 'h6', - } - : undefined - } + titleTypographyProps={{ + ...(size === 'small' ? { variant: 'subtitle2' } : undefined), + }} + subheaderTypographyProps={{ + ...(size === 'small' ? { variant: 'body2' } : undefined), + }} > diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index 1f41a315b8..e80c0d3384 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -155,6 +155,7 @@ export type Props = { className?: string; noPadding?: boolean; titleTypographyProps?: object; + subheaderTypographyProps?: object; }; /** @@ -185,6 +186,7 @@ export function InfoCard(props: Props): JSX.Element { className, noPadding, titleTypographyProps, + subheaderTypographyProps, } = props; const classes = useStyles(); /** @@ -246,6 +248,7 @@ export function InfoCard(props: Props): JSX.Element { action={action} style={{ ...headerStyle }} titleTypographyProps={titleTypographyProps} + subheaderTypographyProps={subheaderTypographyProps} {...headerProps} /> )} From f72f3a076dd911a2f836b5a8b534e6e0fbe0c885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Sat, 6 Apr 2024 09:32:23 +0200 Subject: [PATCH 077/567] fix: Made the subhead in InfoCard not duplicate top padding props twice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- packages/core-components/src/layout/InfoCard/InfoCard.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index e80c0d3384..a08a816c2e 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -217,10 +217,7 @@ export function InfoCard(props: Props): JSX.Element { } return ( -
+
{subheader &&
{subheader}
} {icon}
From 96cd13eca2ddc488a35f9bbb11ae7fdba29c22bf Mon Sep 17 00:00:00 2001 From: Benjamin Janssens Date: Tue, 23 Apr 2024 12:36:38 +0200 Subject: [PATCH 078/567] feat: add property ownerPickerMode to TechDocsIndexPage and DefaultApiExplorerPage Signed-off-by: Benjamin Janssens --- .changeset/gentle-baboons-peel.md | 5 +++++ .changeset/tiny-pandas-return.md | 5 +++++ plugins/api-docs/api-report.md | 2 ++ .../ApiExplorerPage/DefaultApiExplorerPage.tsx | 11 +++++++++-- plugins/techdocs/api-report.md | 2 ++ .../src/home/components/DefaultTechDocsHome.tsx | 4 ++-- .../src/home/components/TechDocsIndexPage.tsx | 6 +++++- 7 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 .changeset/gentle-baboons-peel.md create mode 100644 .changeset/tiny-pandas-return.md diff --git a/.changeset/gentle-baboons-peel.md b/.changeset/gentle-baboons-peel.md new file mode 100644 index 0000000000..d63396860b --- /dev/null +++ b/.changeset/gentle-baboons-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Added property ownerPickerMode to TechDocsIndexPage diff --git a/.changeset/tiny-pandas-return.md b/.changeset/tiny-pandas-return.md new file mode 100644 index 0000000000..d584accccf --- /dev/null +++ b/.changeset/tiny-pandas-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Added property ownerPickerMode to DefaultApiExplorerPage diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index 7523198298..2fc852777b 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -9,6 +9,7 @@ import { ApiEntity } from '@backstage/catalog-model'; import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogTableRow } from '@backstage/plugin-catalog'; +import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; @@ -105,6 +106,7 @@ export type DefaultApiExplorerPageProps = { initiallySelectedFilter?: UserListFilterKind; columns?: TableColumn[]; actions?: TableProps['actions']; + ownerPickerMode?: EntityOwnerPickerProps['mode']; }; // @public (undocumented) diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx index dc83c2528d..6b5917c52d 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx @@ -35,6 +35,7 @@ import { UserListFilterKind, UserListPicker, CatalogFilterLayout, + EntityOwnerPickerProps, } from '@backstage/plugin-catalog-react'; import React from 'react'; import { registerComponentRouteRef } from '../../routes'; @@ -60,6 +61,7 @@ export type DefaultApiExplorerPageProps = { initiallySelectedFilter?: UserListFilterKind; columns?: TableColumn[]; actions?: TableProps['actions']; + ownerPickerMode?: EntityOwnerPickerProps['mode']; }; /** @@ -67,7 +69,12 @@ export type DefaultApiExplorerPageProps = { * @public */ export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => { - const { initiallySelectedFilter = 'all', columns, actions } = props; + const { + initiallySelectedFilter = 'all', + columns, + actions, + ownerPickerMode, + } = props; const configApi = useApi(configApiRef); const generatedSubtitle = `${ @@ -101,7 +108,7 @@ export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => {
- + {canCreateTask ? ( + + ) : null} diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index e4425b04bf..ebc0c3596f 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -23,7 +23,8 @@ import { MockStarredEntitiesApi, } from '@backstage/plugin-catalog-react'; import React from 'react'; -import { scaffolderApiRef, ScaffolderClient } from '../src'; +import { ScaffolderClient } from '../src'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { ScaffolderPage } from '../src/plugin'; import { discoveryApiRef, diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index d6eabb0e5c..7e859e272d 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -43,7 +43,8 @@ import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { CodeSnippet, Content, - ErrorPage, + EmptyState, + ErrorPanel, Header, MarkdownContent, Page, @@ -112,19 +113,9 @@ const ExamplesTable = (props: { examples: ActionExample[] }) => { ); }; -export const ActionsPage = () => { +const ActionPageContent = () => { const api = useApi(scaffolderApiRef); - const navigate = useNavigate(); - const editorLink = useRouteRef(editRouteRef); - const tasksLink = useRouteRef(scaffolderListTaskRouteRef); - const createLink = useRouteRef(rootRouteRef); - const scaffolderPageContextMenuProps = { - onEditorClicked: () => navigate(editorLink()), - onActionsClicked: undefined, - onTasksClicked: () => navigate(tasksLink()), - onCreateClicked: () => navigate(createLink()), - }; const classes = useStyles(); const { loading, value, error } = useAsync(async () => { return api.listActions(); @@ -137,11 +128,14 @@ export const ActionsPage = () => { if (error) { return ( - + <> + + + ); } @@ -282,7 +276,7 @@ export const ActionsPage = () => { ); }; - const items = value?.map(action => { + return value?.map(action => { if (action.id.startsWith('legacy:')) { return undefined; } @@ -336,6 +330,19 @@ export const ActionsPage = () => {
); }); +}; +export const ActionsPage = () => { + const navigate = useNavigate(); + const editorLink = useRouteRef(editRouteRef); + const tasksLink = useRouteRef(scaffolderListTaskRouteRef); + const createLink = useRouteRef(rootRouteRef); + + const scaffolderPageContextMenuProps = { + onEditorClicked: () => navigate(editorLink()), + onActionsClicked: undefined, + onTasksClicked: () => navigate(tasksLink()), + onCreateClicked: () => navigate(createLink()), + }; return ( @@ -346,7 +353,9 @@ export const ActionsPage = () => { > - {items} + + + ); }; diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index 4c2eb04c79..d37716a710 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -77,7 +77,7 @@ const ListTaskPageContent = (props: MyTaskPageProps) => { ); diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 99d9235791..bac23da67d 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -30,6 +30,12 @@ import MoreVert from '@material-ui/icons/MoreVert'; import React, { useState } from 'react'; import { useApi } from '@backstage/core-plugin-api'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { + taskCancelPermission, + taskReadPermission, + taskCreatePermission, +} from '@backstage/plugin-scaffolder-common/alpha'; type ContextMenuProps = { cancelEnabled?: boolean; @@ -69,6 +75,28 @@ export const ContextMenu = (props: ContextMenuProps) => { } }); + // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` + const { allowed: canCancelTask } = usePermission({ + permission: taskCancelPermission, + resourceRef: 'task', + }); + + const { allowed: canReadTask } = usePermission({ + permission: taskReadPermission, + resourceRef: 'task', + }); + + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + resourceRef: 'task', + }); + + // Cancel endpoint requires user to have both read and cancel permissions + const cancelNotAllowed = !(canReadTask && canCancelTask); + + // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions + const canStartOver = canReadTask && canCreateTask; + return ( <> { primary={buttonBarVisible ? 'Hide Button Bar' : 'Show Button Bar'} /> - + @@ -113,7 +145,11 @@ export const ContextMenu = (props: ContextMenuProps) => { diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx index 9d3f29fe7b..70d2193470 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.test.tsx @@ -16,10 +16,20 @@ import { OngoingTask } from './OngoingTask'; import React from 'react'; -import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { + renderInTestApp, + TestApiProvider, + MockPermissionApi, +} from '@backstage/test-utils'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; import { act, fireEvent, waitFor, within } from '@testing-library/react'; +import { + PermissionApi, + permissionApiRef, +} from '@backstage/plugin-permission-react'; import { rootRouteRef } from '../../routes'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { SWRConfig } from 'swr'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -49,18 +59,29 @@ describe('OngoingTask', () => { getTask: jest.fn().mockImplementation(async () => {}), }; - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); }); - it('should trigger cancel api on "Cancel" click in context menu', async () => { - const cancelOptionLabel = 'Cancel'; - const rendered = await renderInTestApp( - - - , + const render = (permissionApi?: PermissionApi) => { + // SWR used by the usePermission hook needs cache to be reset for each test + return renderInTestApp( + new Map() }}> + + + + , { mountedRoutes: { '/': rootRouteRef } }, ); + }; + it('should trigger cancel api on "Cancel" click in context menu', async () => { + const rendered = await render(); + const cancelOptionLabel = 'Cancel'; const { getByTestId } = rendered; await act(async () => { @@ -84,13 +105,9 @@ describe('OngoingTask', () => { }); it('should trigger cancel api on "Cancel" button click', async () => { + const rendered = await render(); const cancelOptionLabel = 'Cancel'; - const rendered = await renderInTestApp( - - - , - { mountedRoutes: { '/': rootRouteRef } }, - ); + const { getByTestId } = rendered; await act(async () => { @@ -114,22 +131,12 @@ describe('OngoingTask', () => { }); it('should initially do not display logs', async () => { - const rendered = await renderInTestApp( - - - , - { mountedRoutes: { '/': rootRouteRef } }, - ); + const rendered = await render(); await expect(rendered.findByText('Show Logs')).resolves.toBeInTheDocument(); }); it('should toggle logs visibility', async () => { - const rendered = await renderInTestApp( - - - , - { mountedRoutes: { '/': rootRouteRef } }, - ); + const rendered = await render(); await act(async () => { const element = await rendered.findByText('Show Logs'); fireEvent.click(element); @@ -137,4 +144,22 @@ describe('OngoingTask', () => { await expect(rendered.findByText('Hide Logs')).resolves.toBeInTheDocument(); }); + + it('should have cancel and start over buttons be disabled without the proper permissions', async () => { + const mockAuthorize = jest + .fn() + .mockImplementation(async () => ({ result: AuthorizeResult.DENY })); + const permissionApi: PermissionApi = { authorize: mockAuthorize }; + const rendered = await render(permissionApi); + + const { getByTestId } = rendered; + expect(getByTestId('cancel-button')).toHaveClass('Mui-disabled'); + expect(getByTestId('start-over-button')).toHaveClass('Mui-disabled'); + + await act(async () => { + fireEvent.click(getByTestId('menu-button')); + }); + expect(getByTestId('cancel-task')).toHaveClass('Mui-disabled'); + expect(getByTestId('start-over-task')).toHaveClass('Mui-disabled'); + }); }); diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 3e7a3b8f52..d5f6dcfb0d 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -35,6 +35,12 @@ import { TaskSteps, } from '@backstage/plugin-scaffolder-react/alpha'; import { useAsync } from '@react-hookz/web'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { + taskCancelPermission, + taskReadPermission, + taskCreatePermission, +} from '@backstage/plugin-scaffolder-common/alpha'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -81,6 +87,28 @@ export const OngoingTask = (props: { const [logsVisible, setLogVisibleState] = useState(false); const [buttonBarVisible, setButtonBarVisibleState] = useState(true); + // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` + const { allowed: canCancelTask } = usePermission({ + permission: taskCancelPermission, + resourceRef: 'task', + }); + + const { allowed: canReadTask } = usePermission({ + permission: taskReadPermission, + resourceRef: 'task', + }); + + const { allowed: canCreateTask } = usePermission({ + permission: taskCreatePermission, + resourceRef: 'task', + }); + + // Cancel endpoint requires user to have both read and cancel permissions + const cancelNotAllowed = !(canReadTask && canCancelTask); + + // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions + const canStartOver = canReadTask && canCreateTask; + useEffect(() => { if (taskStream.error) { setLogVisibleState(true); @@ -192,7 +220,11 @@ export const OngoingTask = (props: {
From fa26d03a6aab6fed41dc1b08cff88397e242b31e Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 25 Apr 2024 16:43:34 -0400 Subject: [PATCH 091/567] chore: update package.json Signed-off-by: Frank Kong --- plugins/catalog/package.json | 3 ++- plugins/scaffolder/package.json | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index db2089a89e..bc4e125b97 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -87,7 +87,8 @@ "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0", - "@testing-library/user-event": "^14.0.0" + "@testing-library/user-event": "^14.0.0", + "swr": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 32dc701a1e..5c0773bd65 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -98,6 +98,7 @@ "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", "@testing-library/jest-dom": "^6.0.0", @@ -105,7 +106,8 @@ "@testing-library/user-event": "^14.0.0", "@types/humanize-duration": "^3.18.1", "@types/json-schema": "^7.0.9", - "msw": "^1.0.0" + "msw": "^1.0.0", + "swr": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", From bcec60fb4a46137be4ab7ecc3d07170b69c5eb5f Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 25 Apr 2024 17:18:47 -0400 Subject: [PATCH 092/567] chore: add changeset Signed-off-by: Frank Kong --- .changeset/tender-seas-listen.md | 12 ++++++++++++ .changeset/weak-gifts-occur.md | 11 +++++++++++ 2 files changed, 23 insertions(+) create mode 100644 .changeset/tender-seas-listen.md create mode 100644 .changeset/weak-gifts-occur.md diff --git a/.changeset/tender-seas-listen.md b/.changeset/tender-seas-listen.md new file mode 100644 index 0000000000..cfcea07574 --- /dev/null +++ b/.changeset/tender-seas-listen.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-catalog': patch +--- + +updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + +- `scaffolder.task.create` +- `scaffolder.task.cancel` +- `scaffolder.task.read` +- `scaffolder.action.read` diff --git a/.changeset/weak-gifts-occur.md b/.changeset/weak-gifts-occur.md new file mode 100644 index 0000000000..c1a65e7dff --- /dev/null +++ b/.changeset/weak-gifts-occur.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-common': patch +--- + +added the following new permissions to the scaffolder backend endpoints: + +- `scaffolder.task.create` +- `scaffolder.task.cancel` +- `scaffolder.task.read` +- `scaffolder.action.read` From 3ef8cbc9cc4cf99782aedbfaabc6fe3344ddf4c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 00:20:29 +0000 Subject: [PATCH 093/567] chore(deps): update github/codeql-action action to v3.25.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/verify_codeql.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 91f1b02fee..67fdff4020 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -66,6 +66,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 70fb8a44d9..e2507996b1 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: sarif_file: snyk.sarif diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index e82577763e..920c9bab17 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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 # ℹ️ 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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 From 3383f82c58e0f344a7e530bb6096e43b11a6a763 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Fri, 26 Apr 2024 01:28:40 -0300 Subject: [PATCH 094/567] feat: no need to pass value in autocomplete as we have renderOptions Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../MultiEntityPicker/MultiEntityPicker.tsx | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index 0a0fcfdc2b..073b364720 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -71,14 +71,8 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { const { items } = await catalogApi.getEntities( catalogFilter ? { filter: catalogFilter } : undefined, ); - const primaryTitles: string[] = []; - for (const item of items) { - const entityPresentation = (await entityPresentationApi.forEntity(item) - ?.promise) as EntityRefPresentationSnapshot[]; - entityPresentation.map(e => primaryTitles.push(e.primaryTitle)); - } - return { items, primaryTitles }; + return items; }); const allowArbitraryValues = uiSchema['ui:options']?.allowArbitraryValues ?? true; @@ -134,10 +128,10 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { ); useEffect(() => { - if (entities?.items?.length === 1) { - onChange([stringifyEntityRef(entities.items[0])]); + if (entities?.length === 1) { + onChange([stringifyEntityRef(entities[0])]); } - }, [entities?.items, onChange]); + }, [entities, onChange]); return ( { formData && formData.includes(stringifyEntityRef(e)), - ) ?? - (allowArbitraryValues && formData - ? entities?.primaryTitles || [] - : []) - } loading={loading} onChange={onSelect} - options={entities?.items || []} + options={entities || []} renderOption={option => } getOptionLabel={option => // option can be a string due to freeSolo. From 90db1ce4410c44dc3574340072791aca7ca80c1f Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Fri, 26 Apr 2024 01:33:16 -0300 Subject: [PATCH 095/567] chore: eslint error resolved Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../components/fields/MultiEntityPicker/MultiEntityPicker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index 073b364720..85e08740df 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -26,7 +26,6 @@ import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, entityPresentationApiRef, - EntityRefPresentationSnapshot, EntityDisplayName, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; From 030fd36b03e5d13bfe54f369aa59a331b50e1523 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Mon, 11 Mar 2024 22:55:16 +0000 Subject: [PATCH 096/567] Enabling scaffolder dry run Signed-off-by: Tavi Nolan --- .../src/actions/githubPullRequest.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index b4aa03b602..243c871724 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -14,24 +14,25 @@ * limitations under the License. */ -import path from 'path'; +import { CustomErrorBase, InputError } from '@backstage/errors'; import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; import { + SerializedFile, createTemplateAction, parseRepoUrl, - SerializedFile, serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; + +import { Logger } from 'winston'; import { Octokit } from 'octokit'; -import { CustomErrorBase, InputError } from '@backstage/errors'; -import { resolveSafeChildPath } from '@backstage/backend-common'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; -import { getOctokitOptions } from './helpers'; import { examples } from './githubPullRequest.examples'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { getOctokitOptions } from './helpers'; +import path from 'path'; +import { resolveSafeChildPath } from '@backstage/backend-common'; export type Encoding = 'utf-8' | 'base64'; @@ -139,6 +140,7 @@ export const createPublishGithubPullRequestAction = ( }>({ id: 'publish:github:pull-request', examples, + supportsDryRun: true, schema: { input: { required: ['repoUrl', 'title', 'description', 'branchName'], From 9f3fdcc3828ccb7c0c73554b1c86b088c4d08f2a Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Wed, 3 Apr 2024 13:28:07 +0100 Subject: [PATCH 097/567] initial commit, add dry run to githubPullRequest Signed-off-by: Tavi Nolan --- .../src/actions/githubPullRequest.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 243c871724..12ff50afc4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -321,6 +321,22 @@ export const createPublishGithubPullRequestAction = ( ]), ); + if (ctx.isDryRun) { + ctx.logger.info( + `Dry run arguments: ${{ + repoUrl, + branchName, + title, + description, + ...ctx.input, + }}`, + ); + ctx.output('targetBranchName', branchName); + ctx.output('remoteUrl', repoUrl); + ctx.output('pullRequestNumber', 42); + return; + } + try { const createOptions: createPullRequest.Options = { owner, From 27dbbdf72a1c442cd3e1434c47ceb038751d766d Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Wed, 3 Apr 2024 13:41:54 +0100 Subject: [PATCH 098/567] fixed imports Signed-off-by: Tavi Nolan --- .../src/actions/githubPullRequest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 12ff50afc4..a1606a0d95 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -333,7 +333,7 @@ export const createPublishGithubPullRequestAction = ( ); ctx.output('targetBranchName', branchName); ctx.output('remoteUrl', repoUrl); - ctx.output('pullRequestNumber', 42); + ctx.output('pullRequestNumber', 43); return; } From dae724d913693ca77360979ce1dc128bad17c9f3 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Wed, 3 Apr 2024 14:03:59 +0100 Subject: [PATCH 099/567] add dry run to githubWebhook Signed-off-by: Tavi Nolan --- .../src/actions/githubWebhook.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 984029076e..e8bf179e75 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -18,15 +18,16 @@ import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; +import { InputError, assertError } from '@backstage/errors'; import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; -import { emitterEventNames } from '@octokit/webhooks'; -import { assertError, InputError } from '@backstage/errors'; + import { Octokit } from 'octokit'; -import { getOctokitOptions } from './helpers'; +import { emitterEventNames } from '@octokit/webhooks'; import { examples } from './githubWebhook.examples'; +import { getOctokitOptions } from './helpers'; /** * Creates new action that creates a webhook for a repository on GitHub. @@ -148,6 +149,19 @@ export function createGithubWebhookAction(options: { }), ); + if (ctx.isDryRun) { + ctx.logger.info( + `Dry run arguments: ${{ + repoUrl, + webhookUrl, + webhookSecret, + events, + ...ctx.input, + }}`, + ); + return; + } + try { const insecure_ssl = insecureSsl ? '1' : '0'; await client.rest.repos.createWebhook({ From f0ae76208118652b61115462a9d858f7cc8a0161 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 13:49:49 +0100 Subject: [PATCH 100/567] working test for githubPullRequest Signed-off-by: Tavi Nolan --- .../src/actions/githubPullRequest.test.ts | 31 +++++++++++++------ .../src/actions/githubPullRequest.ts | 4 --- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index c9d1498187..86197285f2 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -14,20 +14,21 @@ * limitations under the License. */ -import { createRootLogger } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; -import { - GithubCredentialsProvider, - ScmIntegrations, -} from '@backstage/integration'; import { ActionContext, TemplateAction, } from '@backstage/plugin-scaffolder-node'; -import fs from 'fs-extra'; -import { createPublishGithubPullRequestAction } from './githubPullRequest'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; + +import { ConfigReader } from '@backstage/config'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { createPublishGithubPullRequestAction } from './githubPullRequest'; +import { createRootLogger } from '@backstage/backend-common'; +import fs from 'fs-extra'; // Make sure root logger is initialized ahead of FS mock createRootLogger(); @@ -227,6 +228,18 @@ describe('createPublishGithubPullRequestAction', () => { ); expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); + + it('handles dry run correctly', async () => { + ctx.isDryRun = true; + await instance.handler(ctx); + + expect(ctx.output).toHaveBeenCalledWith('targetBranchName', 'new-app'); + expect(ctx.output).toHaveBeenCalledWith( + 'remoteUrl', + 'github.com?owner=myorg&repo=myrepo', + ); + expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 43); + }); }); describe('with sourcePath', () => { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index a1606a0d95..e058bfcd94 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -324,10 +324,6 @@ export const createPublishGithubPullRequestAction = ( if (ctx.isDryRun) { ctx.logger.info( `Dry run arguments: ${{ - repoUrl, - branchName, - title, - description, ...ctx.input, }}`, ); From 8de90c83587ada096900730bf8b7709d0148f0d2 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 14:14:55 +0100 Subject: [PATCH 101/567] testing for githubWebhook.test.ts Signed-off-by: Tavi Nolan --- .../src/actions/githubWebhook.test.ts | 40 +++++++++++++++++-- .../src/actions/githubWebhook.ts | 2 - 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts index cbdcee6af1..367f19d083 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts @@ -14,15 +14,16 @@ * limitations under the License. */ -import { createGithubWebhookAction } from './githubWebhook'; import { - ScmIntegrations, DefaultGithubCredentialsProvider, GithubCredentialsProvider, + ScmIntegrations, } from '@backstage/integration'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; + import { ConfigReader } from '@backstage/config'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createGithubWebhookAction } from './githubWebhook'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; const mockOctokit = { rest: { @@ -221,6 +222,39 @@ describe('github:repository:webhook:create', () => { }); }); + it('should call the githubApi for creating repository Webhook with dry run', async () => { + const repoUrl = 'github.com?repo=repo&owner=owner'; + const webhookUrl = 'https://example.com/payload'; + const ctx = Object.assign({}, mockContext, { + input: { repoUrl, webhookUrl }, + }); + ctx.isDryRun = true; + await action.handler(ctx); + + const webhookSecret = 'yet_another_secret'; + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + webhookSecret, + events: ['push', 'pull_request'], + }, + }); + + expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + events: ['push', 'pull_request'], + active: true, + config: { + url: webhookUrl, + content_type: 'form', + secret: webhookSecret, + insecure_ssl: '0', + }, + }); + }); + it('should validate input', async () => { const Validator = require('jsonschema').Validator; const v = new Validator(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index e8bf179e75..aa8f542df7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -152,8 +152,6 @@ export function createGithubWebhookAction(options: { if (ctx.isDryRun) { ctx.logger.info( `Dry run arguments: ${{ - repoUrl, - webhookUrl, webhookSecret, events, ...ctx.input, From cbbde942d76a07e6ea84fd2fb646472e398f5f04 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 14:55:54 +0100 Subject: [PATCH 102/567] updates to githubWebhook Signed-off-by: Tavi Nolan --- .../src/actions/githubWebhook.test.ts | 36 +++++++------------ .../src/actions/githubWebhook.ts | 15 ++++---- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts index 367f19d083..6c0335c509 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts @@ -222,37 +222,27 @@ describe('github:repository:webhook:create', () => { }); }); - it('should call the githubApi for creating repository Webhook with dry run', async () => { + it('should not call the githubApi for creating repository Webhook with dry run', async () => { + // Define constants for reused string literals const repoUrl = 'github.com?repo=repo&owner=owner'; const webhookUrl = 'https://example.com/payload'; - const ctx = Object.assign({}, mockContext, { - input: { repoUrl, webhookUrl }, - }); - ctx.isDryRun = true; - await action.handler(ctx); - const webhookSecret = 'yet_another_secret'; - await action.handler({ + // Create the context object with the necessary properties for a dry run + const ctx = { ...mockContext, + isDryRun: true, input: { ...mockContext.input, - webhookSecret, - events: ['push', 'pull_request'], + repoUrl, + webhookUrl, }, - }); + }; - expect(mockOctokit.rest.repos.createWebhook).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - events: ['push', 'pull_request'], - active: true, - config: { - url: webhookUrl, - content_type: 'form', - secret: webhookSecret, - insecure_ssl: '0', - }, - }); + // Call the handler with the context + await action.handler(ctx); + + // Check that the createWebhook method was not called + expect(mockOctokit.rest.repos.createWebhook).not.toHaveBeenCalled(); }); it('should validate input', async () => { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index aa8f542df7..e0e124a1c0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -149,14 +149,15 @@ export function createGithubWebhookAction(options: { }), ); + // If this is a dry run, log the arguments and exit if (ctx.isDryRun) { - ctx.logger.info( - `Dry run arguments: ${{ - webhookSecret, - events, - ...ctx.input, - }}`, - ); + const dryRunArgs = { + webhookSecret, + events, + ...ctx.input, + }; + + ctx.logger.info(`Dry run completed`); return; } From 38b1a35a83f549dc8ff598972febf878461be6bc Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 16:21:03 +0100 Subject: [PATCH 103/567] updated githubPullRequest Signed-off-by: Tavi Nolan --- .../src/actions/githubPullRequest.test.ts | 2 +- .../src/actions/githubPullRequest.ts | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index 86197285f2..150ed39e50 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -229,7 +229,7 @@ describe('createPublishGithubPullRequestAction', () => { expect(ctx.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); - it('handles dry run correctly', async () => { + it('sets correct outputs during dry run', async () => { ctx.isDryRun = true; await instance.handler(ctx); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index e058bfcd94..99e442f74a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -322,14 +322,11 @@ export const createPublishGithubPullRequestAction = ( ); if (ctx.isDryRun) { - ctx.logger.info( - `Dry run arguments: ${{ - ...ctx.input, - }}`, - ); + ctx.logger.info(`Performing dry run of creating pull request`); ctx.output('targetBranchName', branchName); ctx.output('remoteUrl', repoUrl); ctx.output('pullRequestNumber', 43); + ctx.logger.info(`Dry run complete`); return; } From 6dcd72ae3e7d00190708e100300a42dc1a263445 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 16:25:40 +0100 Subject: [PATCH 104/567] updated githubWebhook variable and comment Signed-off-by: Tavi Nolan --- .../src/actions/githubWebhook.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index e0e124a1c0..9b3ccdac07 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -149,14 +149,8 @@ export function createGithubWebhookAction(options: { }), ); - // If this is a dry run, log the arguments and exit + // If this is a dry run, log and return if (ctx.isDryRun) { - const dryRunArgs = { - webhookSecret, - events, - ...ctx.input, - }; - ctx.logger.info(`Dry run completed`); return; } From 07a6d7f153ced344978a0eff2db35332f5c6c270 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 17:10:52 +0100 Subject: [PATCH 105/567] initial working changes for githubRepoCreate Signed-off-by: Tavi Nolan --- .../src/actions/githubRepoCreate.test.ts | 67 ++++++++++++++++--- .../src/actions/githubRepoCreate.ts | 25 ++++--- 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index 1750acdb9a..edd7bb7c9d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -14,8 +14,18 @@ * limitations under the License. */ +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; + +import { ConfigReader } from '@backstage/config'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createGithubRepoCreateAction } from './githubRepoCreate'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { entityRefToName } from './gitHelpers'; +import { when } from 'jest-when'; jest.mock('./gitHelpers', () => { return { @@ -24,15 +34,6 @@ jest.mock('./gitHelpers', () => { }; }); -import { ConfigReader } from '@backstage/config'; -import { - DefaultGithubCredentialsProvider, - GithubCredentialsProvider, - ScmIntegrations, -} from '@backstage/integration'; -import { createGithubRepoCreateAction } from './githubRepoCreate'; -import { entityRefToName } from './gitHelpers'; - const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; const mockOctokit = { @@ -697,4 +698,52 @@ describe('github:repo:create', () => { 'https://github.com/clone/url.git', ); }); + + it('should NOT call the githubApis with the correct values for createInOrg during dry run', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.teams.getByName.mockResolvedValue({ + data: { + name: 'blam', + id: 42, + }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + mockContext.isDryRun = true; + await action.handler(mockContext); + expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalledWith({ + description: 'description', + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + }); + }); + + it('should not call the githubApis with the correct values for createForAuthenticatedUser during dry run', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'User' }, + }); + + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + data: {}, + }); + + mockContext.isDryRun = true; + await action.handler(mockContext); + expect( + mockOctokit.rest.repos.createForAuthenticatedUser, + ).not.toHaveBeenCalled(); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 52bf9be9c0..175aae46aa 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -14,22 +14,24 @@ * limitations under the License. */ -import { InputError } from '@backstage/errors'; +import * as inputProps from './inputProperties'; +import * as outputProps from './outputProperties'; + import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { Octokit } from 'octokit'; -import { - createTemplateAction, - parseRepoUrl, -} from '@backstage/plugin-scaffolder-node'; import { createGithubRepoWithCollaboratorsAndTopics, getOctokitOptions, } from './helpers'; -import * as inputProps from './inputProperties'; -import * as outputProps from './outputProperties'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; + +import { InputError } from '@backstage/errors'; +import { Octokit } from 'octokit'; import { examples } from './githubRepoCreate.examples'; /** @@ -188,6 +190,13 @@ export function createGithubRepoCreateAction(options: { throw new InputError('Invalid repository owner provided in repoUrl'); } + if (ctx.isDryRun) { + ctx.logger.info(`Performing dry run of creating repository`); + ctx.output('remoteUrl', repoUrl); + ctx.logger.info(`Dry run complete`); + return; + } + const newRepo = await createGithubRepoWithCollaboratorsAndTopics( client, repo, From 1d0a7ce72a9c53f23d617ebc90fd3c1d97aa0893 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 17:19:28 +0100 Subject: [PATCH 106/567] minor fixes and test name changes for githubRepoCreate Signed-off-by: Tavi Nolan --- .../src/actions/githubRepoCreate.test.ts | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index edd7bb7c9d..3940a71c5d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -699,7 +699,7 @@ describe('github:repo:create', () => { ); }); - it('should NOT call the githubApis with the correct values for createInOrg during dry run', async () => { + it('should not call createInOrg during dry run', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'Organization' }, }); @@ -715,23 +715,10 @@ describe('github:repo:create', () => { mockContext.isDryRun = true; await action.handler(mockContext); - expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalledWith({ - description: 'description', - name: 'repo', - org: 'owner', - private: true, - delete_branch_on_merge: false, - allow_squash_merge: true, - squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', - squash_merge_commit_message: 'COMMIT_MESSAGES', - allow_merge_commit: true, - allow_rebase_merge: true, - allow_auto_merge: false, - visibility: 'private', - }); + expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalled(); }); - it('should not call the githubApis with the correct values for createForAuthenticatedUser during dry run', async () => { + it('should not call createForAuthenticatedUser during dry run', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); @@ -740,7 +727,6 @@ describe('github:repo:create', () => { data: {}, }); - mockContext.isDryRun = true; await action.handler(mockContext); expect( mockOctokit.rest.repos.createForAuthenticatedUser, From 7c2c8e4961b04366cebfc28b45f737831a8c71bf Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 17:25:57 +0100 Subject: [PATCH 107/567] added dry run to github.ts Signed-off-by: Tavi Nolan --- .../src/actions/github.test.ts | 46 +++++++++++++++++-- .../src/actions/github.ts | 27 +++++++---- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index 9eda9a2eb0..4802fd9078 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -33,21 +33,23 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { }; }); -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { ConfigReader } from '@backstage/config'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { DefaultGithubCredentialsProvider, GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { createPublishGithubAction } from './github'; -import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import { enableBranchProtectionOnDefaultRepoBranch, entityRefToName, } from './gitHelpers'; +import { ConfigReader } from '@backstage/config'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createPublishGithubAction } from './github'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; +import { when } from 'jest-when'; + const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; const initRepoAndPushMocked = initRepoAndPush as jest.Mock< @@ -2064,4 +2066,38 @@ describe('publish:github', () => { requiredCommitSigning: false, }); }); + + it('should not call createInOrg during dry run', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.teams.getByName.mockResolvedValue({ + data: { + name: 'blam', + id: 42, + }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + mockContext.isDryRun = true; + await action.handler(mockContext); + expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalled(); + }); + + it('should not call createForAuthenticatedUser during dry run', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'User' }, + }); + + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + data: {}, + }); + + await action.handler(mockContext); + expect( + mockOctokit.rest.repos.createForAuthenticatedUser, + ).not.toHaveBeenCalled(); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index f32b65bc9a..923190465a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -14,24 +14,26 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import { InputError } from '@backstage/errors'; +import * as inputProps from './inputProperties'; +import * as outputProps from './outputProperties'; + import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { Octokit } from 'octokit'; -import { - createTemplateAction, - parseRepoUrl, -} from '@backstage/plugin-scaffolder-node'; import { createGithubRepoWithCollaboratorsAndTopics, getOctokitOptions, initRepoPushAndProtect, } from './helpers'; -import * as inputProps from './inputProperties'; -import * as outputProps from './outputProperties'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; + +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { Octokit } from 'octokit'; import { examples } from './github.examples'; /** @@ -227,6 +229,13 @@ export function createPublishGithubAction(options: { throw new InputError('Invalid repository owner provided in repoUrl'); } + if (ctx.isDryRun) { + ctx.logger.info(`Performing dry run of creating repository`); + ctx.output('remoteUrl', repoUrl); + ctx.logger.info(`Dry run complete`); + return; + } + const newRepo = await createGithubRepoWithCollaboratorsAndTopics( client, repo, From d66ca81a3836786eb646f39ce5d3ce241c83db66 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 4 Apr 2024 17:38:42 +0100 Subject: [PATCH 108/567] fixed outputs for github and githubRepoCreate, added dry run for githubRepoPush Signed-off-by: Tavi Nolan --- .../src/actions/github.test.ts | 18 ++++++++++++++++ .../src/actions/github.ts | 4 +++- .../src/actions/githubRepoCreate.test.ts | 8 +++++++ .../src/actions/githubRepoCreate.ts | 2 +- .../src/actions/githubRepoPush.ts | 21 ++++++++++++++----- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index 4802fd9078..a346781428 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -2083,6 +2083,15 @@ describe('publish:github', () => { mockContext.isDryRun = true; await action.handler(mockContext); + expect(mockContext.output).toHaveBeenCalledWith('commitHash', 'commitHash'); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'www.example.com', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'www.example.com/contents', + ); expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalled(); }); @@ -2096,6 +2105,15 @@ describe('publish:github', () => { }); await action.handler(mockContext); + expect(mockContext.output).toHaveBeenCalledWith('commitHash', 'commitHash'); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'www.example.com', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'www.example.com/contents', + ); expect( mockOctokit.rest.repos.createForAuthenticatedUser, ).not.toHaveBeenCalled(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index 923190465a..ab7375daaa 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -231,7 +231,9 @@ export function createPublishGithubAction(options: { if (ctx.isDryRun) { ctx.logger.info(`Performing dry run of creating repository`); - ctx.output('remoteUrl', repoUrl); + ctx.output('commitHash', 'commitHash'); + ctx.output('remoteUrl', 'www.example.com'); + ctx.output('repoContentsUrl', 'www.example.com/contents'); ctx.logger.info(`Dry run complete`); return; } diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index 3940a71c5d..ebd82f0f06 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -715,6 +715,10 @@ describe('github:repo:create', () => { mockContext.isDryRun = true; await action.handler(mockContext); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'www.example.com', + ); expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalled(); }); @@ -728,6 +732,10 @@ describe('github:repo:create', () => { }); await action.handler(mockContext); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'www.example.com', + ); expect( mockOctokit.rest.repos.createForAuthenticatedUser, ).not.toHaveBeenCalled(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 175aae46aa..80edb219df 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -192,7 +192,7 @@ export function createGithubRepoCreateAction(options: { if (ctx.isDryRun) { ctx.logger.info(`Performing dry run of creating repository`); - ctx.output('remoteUrl', repoUrl); + ctx.output('remoteUrl', 'www.example.com'); ctx.logger.info(`Dry run complete`); return; } diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts index 5b9db78f48..5b10226b3d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts @@ -14,20 +14,22 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import { InputError } from '@backstage/errors'; +import * as inputProps from './inputProperties'; +import * as outputProps from './outputProperties'; + import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { Octokit } from 'octokit'; import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { getOctokitOptions, initRepoPushAndProtect } from './helpers'; -import * as inputProps from './inputProperties'; -import * as outputProps from './outputProperties'; + +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { Octokit } from 'octokit'; import { examples } from './githubRepoPush.examples'; /** @@ -156,6 +158,15 @@ export function createGithubRepoPushAction(options: { const remoteUrl = targetRepo.data.clone_url; const repoContentsUrl = `${targetRepo.data.html_url}/blob/${defaultBranch}`; + if (ctx.isDryRun) { + ctx.logger.info(`Performing dry run of creating pull request`); + ctx.output('remoteUrl', 'www.example.com'); + ctx.output('repoContentsUrl', 'www.example.com/content'); + ctx.output('commitHash', 'commitHash'); + ctx.logger.info(`Dry run complete`); + return; + } + const { commitHash } = await initRepoPushAndProtect( remoteUrl, octokitOptions.auth, From 088a586cccd6065984f1b6dfb71d6c8401ab37e7 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Mon, 8 Apr 2024 10:13:00 +0100 Subject: [PATCH 109/567] enabling dry run in githubWebhook Signed-off-by: Tavi Nolan --- .../src/actions/githubWebhook.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 9b3ccdac07..69988f512e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -56,6 +56,7 @@ export function createGithubWebhookAction(options: { id: 'github:webhook', description: 'Creates webhook for a repository on GitHub.', examples, + supportsDryRun: true, schema: { input: { type: 'object', From 425d9d5a43b9a787f9e2c3719bc7234e96b8832d Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 11 Apr 2024 11:11:19 +0100 Subject: [PATCH 110/567] log output change Signed-off-by: Tavi Nolan --- .../src/actions/githubWebhook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 69988f512e..91db9c0534 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -152,7 +152,7 @@ export function createGithubWebhookAction(options: { // If this is a dry run, log and return if (ctx.isDryRun) { - ctx.logger.info(`Dry run completed`); + ctx.logger.info(`Dry run complete`); return; } From 41f70d21d75c1946bf5936b8e5ad748bdbdd3989 Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Thu, 11 Apr 2024 11:15:39 +0100 Subject: [PATCH 111/567] reverting changes to github action Signed-off-by: Tavi Nolan --- .../src/actions/github.test.ts | 52 ------------------- .../src/actions/github.ts | 9 ---- 2 files changed, 61 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index a346781428..73fa7fbcd0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -2066,56 +2066,4 @@ describe('publish:github', () => { requiredCommitSigning: false, }); }); - - it('should not call createInOrg during dry run', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'Organization' }, - }); - - mockOctokit.rest.teams.getByName.mockResolvedValue({ - data: { - name: 'blam', - id: 42, - }, - }); - - mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); - - mockContext.isDryRun = true; - await action.handler(mockContext); - expect(mockContext.output).toHaveBeenCalledWith('commitHash', 'commitHash'); - expect(mockContext.output).toHaveBeenCalledWith( - 'remoteUrl', - 'www.example.com', - ); - expect(mockContext.output).toHaveBeenCalledWith( - 'repoContentsUrl', - 'www.example.com/contents', - ); - expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalled(); - }); - - it('should not call createForAuthenticatedUser during dry run', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: {}, - }); - - await action.handler(mockContext); - expect(mockContext.output).toHaveBeenCalledWith('commitHash', 'commitHash'); - expect(mockContext.output).toHaveBeenCalledWith( - 'remoteUrl', - 'www.example.com', - ); - expect(mockContext.output).toHaveBeenCalledWith( - 'repoContentsUrl', - 'www.example.com/contents', - ); - expect( - mockOctokit.rest.repos.createForAuthenticatedUser, - ).not.toHaveBeenCalled(); - }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index ab7375daaa..63284002cc 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -229,15 +229,6 @@ export function createPublishGithubAction(options: { throw new InputError('Invalid repository owner provided in repoUrl'); } - if (ctx.isDryRun) { - ctx.logger.info(`Performing dry run of creating repository`); - ctx.output('commitHash', 'commitHash'); - ctx.output('remoteUrl', 'www.example.com'); - ctx.output('repoContentsUrl', 'www.example.com/contents'); - ctx.logger.info(`Dry run complete`); - return; - } - const newRepo = await createGithubRepoWithCollaboratorsAndTopics( client, repo, From a0a3c055cd7e4e0caec07024182e75d9262da27f Mon Sep 17 00:00:00 2001 From: "Nolan, Tavi" Date: Thu, 11 Apr 2024 11:18:36 +0100 Subject: [PATCH 112/567] Update github.test.ts Signed-off-by: Nolan, Tavi --- .../src/actions/github.test.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index 73fa7fbcd0..58c43616e4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -33,23 +33,22 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { }; }); +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { ConfigReader } from '@backstage/config'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { DefaultGithubCredentialsProvider, GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; +import { when } from 'jest-when'; +import { createPublishGithubAction } from './github'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import { enableBranchProtectionOnDefaultRepoBranch, entityRefToName, } from './gitHelpers'; -import { ConfigReader } from '@backstage/config'; -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { createPublishGithubAction } from './github'; -import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; -import { when } from 'jest-when'; - const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; const initRepoAndPushMocked = initRepoAndPush as jest.Mock< From c465caddc59a7aa4c6bf181151c99f0d4f3e5d90 Mon Sep 17 00:00:00 2001 From: "Nolan, Tavi" Date: Thu, 11 Apr 2024 11:18:59 +0100 Subject: [PATCH 113/567] Update github.ts Signed-off-by: Nolan, Tavi --- .../src/actions/github.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index 63284002cc..f32b65bc9a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -14,26 +14,24 @@ * limitations under the License. */ -import * as inputProps from './inputProperties'; -import * as outputProps from './outputProperties'; - +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; +import { Octokit } from 'octokit'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { createGithubRepoWithCollaboratorsAndTopics, getOctokitOptions, initRepoPushAndProtect, } from './helpers'; -import { - createTemplateAction, - parseRepoUrl, -} from '@backstage/plugin-scaffolder-node'; - -import { Config } from '@backstage/config'; -import { InputError } from '@backstage/errors'; -import { Octokit } from 'octokit'; +import * as inputProps from './inputProperties'; +import * as outputProps from './outputProperties'; import { examples } from './github.examples'; /** From 565f2a5eba3c42b44fd0dd74617b1af19dd337ad Mon Sep 17 00:00:00 2001 From: "Nolan, Tavi" Date: Thu, 11 Apr 2024 11:19:32 +0100 Subject: [PATCH 114/567] Update githubRepoCreate.test.ts Signed-off-by: Nolan, Tavi --- .../src/actions/githubRepoCreate.test.ts | 62 +++---------------- 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index ebd82f0f06..df2dd29eb8 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -14,18 +14,8 @@ * limitations under the License. */ -import { - DefaultGithubCredentialsProvider, - GithubCredentialsProvider, - ScmIntegrations, -} from '@backstage/integration'; - -import { ConfigReader } from '@backstage/config'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { createGithubRepoCreateAction } from './githubRepoCreate'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { entityRefToName } from './gitHelpers'; -import { when } from 'jest-when'; jest.mock('./gitHelpers', () => { return { @@ -34,6 +24,16 @@ jest.mock('./gitHelpers', () => { }; }); +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { when } from 'jest-when'; +import { createGithubRepoCreateAction } from './githubRepoCreate'; +import { entityRefToName } from './gitHelpers'; + const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; const mockOctokit = { @@ -698,46 +698,4 @@ describe('github:repo:create', () => { 'https://github.com/clone/url.git', ); }); - - it('should not call createInOrg during dry run', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'Organization' }, - }); - - mockOctokit.rest.teams.getByName.mockResolvedValue({ - data: { - name: 'blam', - id: 42, - }, - }); - - mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); - - mockContext.isDryRun = true; - await action.handler(mockContext); - expect(mockContext.output).toHaveBeenCalledWith( - 'remoteUrl', - 'www.example.com', - ); - expect(mockOctokit.rest.repos.createInOrg).not.toHaveBeenCalled(); - }); - - it('should not call createForAuthenticatedUser during dry run', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: {}, - }); - - await action.handler(mockContext); - expect(mockContext.output).toHaveBeenCalledWith( - 'remoteUrl', - 'www.example.com', - ); - expect( - mockOctokit.rest.repos.createForAuthenticatedUser, - ).not.toHaveBeenCalled(); - }); }); From e381232758f330fdb7b3af2735f73f5d08332f42 Mon Sep 17 00:00:00 2001 From: "Nolan, Tavi" Date: Thu, 11 Apr 2024 11:19:51 +0100 Subject: [PATCH 115/567] Update githubRepoCreate.ts Signed-off-by: Nolan, Tavi --- .../src/actions/githubRepoCreate.ts | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 80edb219df..52bf9be9c0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -14,24 +14,22 @@ * limitations under the License. */ -import * as inputProps from './inputProperties'; -import * as outputProps from './outputProperties'; - +import { InputError } from '@backstage/errors'; import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { - createGithubRepoWithCollaboratorsAndTopics, - getOctokitOptions, -} from './helpers'; +import { Octokit } from 'octokit'; import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; - -import { InputError } from '@backstage/errors'; -import { Octokit } from 'octokit'; +import { + createGithubRepoWithCollaboratorsAndTopics, + getOctokitOptions, +} from './helpers'; +import * as inputProps from './inputProperties'; +import * as outputProps from './outputProperties'; import { examples } from './githubRepoCreate.examples'; /** @@ -190,13 +188,6 @@ export function createGithubRepoCreateAction(options: { throw new InputError('Invalid repository owner provided in repoUrl'); } - if (ctx.isDryRun) { - ctx.logger.info(`Performing dry run of creating repository`); - ctx.output('remoteUrl', 'www.example.com'); - ctx.logger.info(`Dry run complete`); - return; - } - const newRepo = await createGithubRepoWithCollaboratorsAndTopics( client, repo, From d1428a0d36a2238c0ee35c01f10c4f90fb2330b6 Mon Sep 17 00:00:00 2001 From: "Nolan, Tavi" Date: Thu, 11 Apr 2024 11:20:11 +0100 Subject: [PATCH 116/567] Update githubRepoPush.ts Signed-off-by: Nolan, Tavi --- .../src/actions/githubRepoPush.ts | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts index 5b10226b3d..5b9db78f48 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts @@ -14,22 +14,20 @@ * limitations under the License. */ -import * as inputProps from './inputProperties'; -import * as outputProps from './outputProperties'; - +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; +import { Octokit } from 'octokit'; import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { getOctokitOptions, initRepoPushAndProtect } from './helpers'; - -import { Config } from '@backstage/config'; -import { InputError } from '@backstage/errors'; -import { Octokit } from 'octokit'; +import * as inputProps from './inputProperties'; +import * as outputProps from './outputProperties'; import { examples } from './githubRepoPush.examples'; /** @@ -158,15 +156,6 @@ export function createGithubRepoPushAction(options: { const remoteUrl = targetRepo.data.clone_url; const repoContentsUrl = `${targetRepo.data.html_url}/blob/${defaultBranch}`; - if (ctx.isDryRun) { - ctx.logger.info(`Performing dry run of creating pull request`); - ctx.output('remoteUrl', 'www.example.com'); - ctx.output('repoContentsUrl', 'www.example.com/content'); - ctx.output('commitHash', 'commitHash'); - ctx.logger.info(`Dry run complete`); - return; - } - const { commitHash } = await initRepoPushAndProtect( remoteUrl, octokitOptions.auth, From 959ed3afb27396b9cf532e708d22bfc7a7113a3a Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 09:07:04 -0400 Subject: [PATCH 117/567] chore: fix tsc Signed-off-by: Frank Kong --- plugins/catalog/src/components/AboutCard/AboutCard.test.tsx | 2 +- .../src/next/components/TemplateCard/TemplateCard.test.tsx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 76d41399ba..fcea659fc6 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -831,7 +831,7 @@ describe('', () => { mockAuthorize.mockImplementation(async () => ({ result: AuthorizeResult.DENY, })); - const rendered = await renderInTestApp( + await renderInTestApp( new Map() }}> Date: Fri, 26 Apr 2024 09:30:26 -0400 Subject: [PATCH 118/567] chore: update api-reports Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 17 ++++++++++++++--- plugins/scaffolder-common/api-report-alpha.md | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 6af1ac04a6..dc7917e825 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -36,6 +36,7 @@ import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; +import { RESOURCE_TYPE_SCAFFOLDER_TASK } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; import { Schema } from 'jsonschema'; @@ -478,10 +479,10 @@ export interface RouterOptions { lifecycle?: LifecycleService; // (undocumented) logger: Logger; + // Warning: (ae-forgotten-export) The symbol "ScaffolderPermissionRuleInput" needs to be exported by the entry point index.d.ts + // // (undocumented) - permissionRules?: Array< - TemplatePermissionRuleInput | ActionPermissionRuleInput - >; + permissionRules?: Array; // (undocumented) permissions?: PermissionsService; // (undocumented) @@ -575,6 +576,16 @@ export class TaskManager implements TaskContext_2 { ): Promise; } +// @public (undocumented) +export type TaskPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule< + TemplateEntityStepV1beta3 | TemplateParametersV1beta3, + {}, + typeof RESOURCE_TYPE_SCAFFOLDER_TASK, + TParams +>; + // @public @deprecated (undocumented) export type TaskSecrets = TaskSecrets_2; diff --git a/plugins/scaffolder-common/api-report-alpha.md b/plugins/scaffolder-common/api-report-alpha.md index de9dffd1b9..3762b0b773 100644 --- a/plugins/scaffolder-common/api-report-alpha.md +++ b/plugins/scaffolder-common/api-report-alpha.md @@ -8,9 +8,15 @@ import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha export const actionExecutePermission: ResourcePermission<'scaffolder-action'>; +// @alpha +export const actionReadPermission: ResourcePermission<'scaffolder-action'>; + // @alpha export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; +// @alpha +export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; + // @alpha export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; @@ -23,9 +29,21 @@ export const scaffolderPermissions: ( | ResourcePermission<'scaffolder-template'> )[]; +// @alpha +export const scaffolderTaskPermissions: ResourcePermission<'scaffolder-task'>[]; + // @alpha export const scaffolderTemplatePermissions: ResourcePermission<'scaffolder-template'>[]; +// @alpha +export const taskCancelPermission: ResourcePermission<'scaffolder-task'>; + +// @alpha +export const taskCreatePermission: ResourcePermission<'scaffolder-task'>; + +// @alpha +export const taskReadPermission: ResourcePermission<'scaffolder-task'>; + // @alpha export const templateParameterReadPermission: ResourcePermission<'scaffolder-template'>; From 2b959e049fd976396f0d97ee621c48deaa5565cb Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 10:21:50 -0400 Subject: [PATCH 119/567] chore: merge dependency imports Signed-off-by: Frank Kong --- plugins/scaffolder-react/src/extensions/rjsf.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-react/src/extensions/rjsf.ts b/plugins/scaffolder-react/src/extensions/rjsf.ts index cacedad080..b81f06758f 100644 --- a/plugins/scaffolder-react/src/extensions/rjsf.ts +++ b/plugins/scaffolder-react/src/extensions/rjsf.ts @@ -14,7 +14,14 @@ * limitations under the License. */ -import { ComponentType, ElementType, FormEvent, ReactNode, Ref } from 'react'; +import { + ComponentType, + ElementType, + FormEvent, + HTMLAttributes, + ReactNode, + Ref, +} from 'react'; import { ErrorSchema, FormContextType, @@ -32,7 +39,6 @@ import { Experimental_DefaultFormStateBehavior, ErrorTransformer, } from '@rjsf/utils'; -import { HTMLAttributes } from 'react'; import Form, { IChangeEvent } from '@rjsf/core'; /** From e4043011e7936cf6ea030850754ae769875f8143 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 12:09:24 -0400 Subject: [PATCH 120/567] chore(scaffolder): fix api-report Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 8 ++++++-- plugins/scaffolder-backend/src/service/router.ts | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index dc7917e825..e99ac56bee 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -479,8 +479,6 @@ export interface RouterOptions { lifecycle?: LifecycleService; // (undocumented) logger: Logger; - // Warning: (ae-forgotten-export) The symbol "ScaffolderPermissionRuleInput" needs to be exported by the entry point index.d.ts - // // (undocumented) permissionRules?: Array; // (undocumented) @@ -501,6 +499,12 @@ export type RunCommandOptions = ExecuteShellCommandOptions; // @public @deprecated export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; +// @public (undocumented) +export type ScaffolderPermissionRuleInput = + | TemplatePermissionRuleInput + | ActionPermissionRuleInput + | TaskPermissionRuleInput; + // @public @deprecated export type SerializedTask = SerializedTask_2; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3fd1e14fa1..228f7bed12 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -104,7 +104,11 @@ import { } from '@backstage/plugin-auth-node'; import { InternalTaskSecrets } from '../scaffolder/tasks/types'; -type ScaffolderPermissionRuleInput = +/** + * + * @public + */ +export type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput | ActionPermissionRuleInput | TaskPermissionRuleInput; From 3078ff09b7654cafde6c59062da6beba37353a70 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 26 Apr 2024 12:31:08 -0400 Subject: [PATCH 121/567] chore: update yarn.lock Signed-off-by: Frank Kong --- yarn.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..55cd380654 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5684,6 +5684,7 @@ __metadata: lodash: ^4.17.21 pluralize: ^8.0.0 react-use: ^17.2.4 + swr: ^2.0.0 zen-observable: ^0.10.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -6877,6 +6878,8 @@ __metadata: "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" + "@backstage/plugin-permission-react": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" @@ -6907,6 +6910,7 @@ __metadata: luxon: ^3.0.0 qs: ^6.9.4 react-use: ^17.2.4 + swr: ^2.0.0 use-immer: ^0.9.0 zen-observable: ^0.10.0 zod: ^3.22.4 @@ -6937,6 +6941,7 @@ __metadata: "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-react": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/plugin-scaffolder-react": "workspace:^" @@ -6973,6 +6978,7 @@ __metadata: msw: ^1.0.0 qs: ^6.9.4 react-use: ^17.2.4 + swr: ^2.0.0 yaml: ^2.0.0 zen-observable: ^0.10.0 zod: ^3.22.4 From 18f736ffc5efbd2e2dcdf08c12fa8c14ca883791 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Sat, 27 Apr 2024 22:22:06 +0530 Subject: [PATCH 122/567] Add examples for scaffolder action & improve related tests Signed-off-by: JeevaRamanathan --- .changeset/tame-jars-double.md | 5 + ...tlabProjectVariableAction.examples.test.ts | 236 ++++++++++++++++++ ...ateGitlabProjectVariableAction.examples.ts | 160 ++++++++++++ .../createGitlabProjectVariableAction.ts | 3 +- 4 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 .changeset/tame-jars-double.md create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts diff --git a/.changeset/tame-jars-double.md b/.changeset/tame-jars-double.md new file mode 100644 index 0000000000..8af92a2d72 --- /dev/null +++ b/.changeset/tame-jars-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +--- + +Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts new file mode 100644 index 0000000000..d1c37ccd9d --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts @@ -0,0 +1,236 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createGitlabProjectVariableAction } from './createGitlabProjectVariableAction'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import yaml from 'yaml'; +import { examples } from './createGitlabProjectVariableAction.examples'; + +const mockGitlabClient = { + ProjectVariables: { + create: jest.fn(), + }, +}; +jest.mock('@gitbeaker/node', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:projectVariableAction: create examples', () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + { + host: 'hosted.gitlab.com', + apiBaseUrl: 'https://api.hosted.gitlab.com', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createGitlabProjectVariableAction({ integrations }); + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`Should ${examples[0].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[0].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + variable_type: 'env_var', + environment_scope: '*', + masked: false, + protected: false, + raw: false, + }, + ); + }); + it(`Should ${examples[1].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[1].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my-file-content', + protected: false, + masked: false, + raw: false, + environment_scope: '*', + variable_type: 'file', + }, + ); + }); + + it(`Should ${examples[2].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[2].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '456', + { + key: 'MY_VARIABLE', + value: 'my_value', + masked: false, + raw: false, + environment_scope: '*', + variable_type: 'env_var', + protected: true, + }, + ); + }); + + it(`Should ${examples[3].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[3].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '789', + { + key: 'DB_PASSWORD', + value: 'password123', + protected: false, + raw: false, + environment_scope: '*', + variable_type: 'env_var', + masked: true, + }, + ); + }); + + it(`Should ${examples[4].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[4].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + protected: false, + environment_scope: '*', + variable_type: 'env_var', + masked: false, + raw: true, + }, + ); + }); + + it(`Should ${examples[5].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[5].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + protected: false, + variable_type: 'env_var', + masked: false, + raw: false, + environment_scope: 'production', + }, + ); + }); + + it(`Should ${examples[6].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[6].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + protected: false, + variable_type: 'env_var', + masked: false, + raw: false, + environment_scope: '*', + }, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts new file mode 100644 index 0000000000..81d526f7c8 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts @@ -0,0 +1,160 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Creating a GitLab project variable of type env_var', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + }, + }, + ], + }), + }, + { + description: 'Creating a GitLab project variable of type file', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my-file-content', + variableType: 'file', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project variable that is protected.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '456', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + variableProtected: true, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project variable with masked flag as true', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '789', + key: 'DB_PASSWORD', + value: 'password123', + variableType: 'env_var', + masked: true, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project variable that is expandable.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:projectVariable:create', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + raw: true, + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project variable with a specific environment scope.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:projectVariable:create', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + environmentScope: 'production', + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project variable with a wildcard environment scope.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:projectVariable:create', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + environmentScope: '*', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts index a154c16662..e09a074701 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts @@ -20,6 +20,7 @@ import { Gitlab } from '@gitbeaker/node'; import { getToken } from '../util'; import commonGitlabConfig from '../commonGitlabConfig'; import { z } from 'zod'; +import { examples } from './createGitlabProjectVariableAction.examples'; /** * Creates a `gitlab:projectVariable:create` Scaffolder action. @@ -33,6 +34,7 @@ export const createGitlabProjectVariableAction = (options: { const { integrations } = options; return createTemplateAction({ id: 'gitlab:projectVariable:create', + examples, schema: { input: commonGitlabConfig.merge( z.object({ @@ -85,7 +87,6 @@ export const createGitlabProjectVariableAction = (options: { host: integrationConfig.config.baseUrl, token: token, }); - await api.ProjectVariables.create(projectId, { key: key, value: value, From 65ec043e4ee3ad75038e52f654e00278f605119c Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Sun, 28 Apr 2024 20:26:04 +0200 Subject: [PATCH 123/567] add some known issues and pickers fix Signed-off-by: Juan Pablo Garcia Ripa --- .changeset/modern-radios-guess.md | 5 ++ .../no-top-level-material-ui-4-imports.md | 47 +++++++++++++++++++ .../no-top-level-material-ui-4-imports.js | 6 ++- ...no-top-level-material-ui-4-imports.test.ts | 5 ++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 .changeset/modern-radios-guess.md diff --git a/.changeset/modern-radios-guess.md b/.changeset/modern-radios-guess.md new file mode 100644 index 0000000000..8b68ebcdf4 --- /dev/null +++ b/.changeset/modern-radios-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/eslint-plugin': patch +--- + +add some `pickers` fixes diff --git a/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md b/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md index a105014abc..e2143354d2 100644 --- a/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md +++ b/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md @@ -41,3 +41,50 @@ import { import Typography from '@material-ui/core/Typography'; import Box from '@material-ui/core/Box'; ``` + +## --fix known issues + +This rule provides automatic fixes for the imports, but it has some known issues: + +### Non Props types import + +The fix will handle correctly 3 groups of imports: + +- Any import from related to styles (i.e `makeStyles`, `styled`, `WithStyles`) will be auto fixed to the `@material-ui/core/styles` import. +- Any import with `Props` suffix will be auto fixed to actual component for example `DialogProps` will be imported from `@material-ui/core/Dialog`. +- Any other import will be considered as a component import and will be auto fixed to the actual component import. + +This means that some types of imports without `Props` suffix will be wrongly auto fixed to the component import, for example this fix will be wrong: + +```diff +- import { Alert, Color } from '@material-ui/lab'; ++ import Alert from '@material-ui/lab/Alert'; ++ import Color from '@material-ui/lab/Color'; // this import is wrong +``` + +The correct import should look like this: + +```diff +- import { Alert, Color } from '@material-ui/lab'; ++ import Alert, {Color} from '@material-ui/lab/Alert'; +``` + +Because `Color` is a type coming from the Alert component. + +### No default export available + +Some components do not have a default export, for example `@material-ui/pickers/DateTimePicker` does not have a default export, so the fix will not work for these cases. + +The fix will be wrong for this import: + +```diff +- import { DateTimePicker } from '@material-ui/pickers'; ++ import DateTimePicker from '@material-ui/pickers/DateTimePicker'; // this default import does not exist +``` + +The correct import should look like this: + +```diff +- import { DateTimePicker } from '@material-ui/pickers'; ++ import { DateTimePicker } from '@material-ui/pickers/DateTimePicker'; // this is the correct import +``` diff --git a/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js index adf04492b4..78fd3d63e9 100644 --- a/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js +++ b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js @@ -155,7 +155,11 @@ module.exports = { const value = s.imported.name; const alias = s.local.name === value ? undefined : s.local.name; - const propsMatch = /^([A-Z]\w+)Props$/.exec(value); + const propsMatch = + /^([A-Z]\w+)Props$/.exec(value) ?? + (node.source.value === '@material-ui/pickers' + ? /^Keyboard([A-Z]\w+Picker)$/.exec(value) + : null); const emitProp = propsMatch !== null; const emitComponent = !emitProp; diff --git a/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts index fdde615c3e..4b675bd038 100644 --- a/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts +++ b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts @@ -161,5 +161,10 @@ import { styled, withStyles, alpha, duration } from '@material-ui/core/styles';` import TreeView from '@material-ui/lab/TreeView'; import { AlertProps } from '@material-ui/lab/Alert';`, }, + { + code: `import { KeyboardDatePicker } from '@material-ui/pickers';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import { KeyboardDatePicker } from '@material-ui/pickers/DatePicker';`, + }, ], }); From ed30d2cb7a4744b532630d5005f05cfe23d74d8b Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Wed, 17 Apr 2024 15:38:21 +0100 Subject: [PATCH 124/567] Fixed imports Signed-off-by: Tavi Nolan --- .../src/actions/githubPullRequest.ts | 16 ++++++++-------- .../src/actions/githubWebhook.ts | 7 +++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 99e442f74a..de70f56399 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -14,25 +14,24 @@ * limitations under the License. */ -import { CustomErrorBase, InputError } from '@backstage/errors'; +import path from 'path'; import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; import { - SerializedFile, createTemplateAction, parseRepoUrl, + SerializedFile, serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; - -import { Logger } from 'winston'; import { Octokit } from 'octokit'; -import { createPullRequest } from 'octokit-plugin-create-pull-request'; -import { examples } from './githubPullRequest.examples'; -import { getOctokitOptions } from './helpers'; -import path from 'path'; +import { CustomErrorBase, InputError } from '@backstage/errors'; import { resolveSafeChildPath } from '@backstage/backend-common'; +import { createPullRequest } from 'octokit-plugin-create-pull-request'; +import { getOctokitOptions } from './helpers'; +import { examples } from './githubPullRequest.examples'; +import { LoggerService } from '@backstage/backend-plugin-api'; export type Encoding = 'utf-8' | 'base64'; @@ -321,6 +320,7 @@ export const createPublishGithubPullRequestAction = ( ]), ); + // If this is a dry run, log and return if (ctx.isDryRun) { ctx.logger.info(`Performing dry run of creating pull request`); ctx.output('targetBranchName', branchName); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 91db9c0534..e188cdf0cb 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -18,16 +18,15 @@ import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { InputError, assertError } from '@backstage/errors'; import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; - -import { Octokit } from 'octokit'; import { emitterEventNames } from '@octokit/webhooks'; -import { examples } from './githubWebhook.examples'; +import { assertError, InputError } from '@backstage/errors'; +import { Octokit } from 'octokit'; import { getOctokitOptions } from './helpers'; +import { examples } from './githubWebhook.examples'; /** * Creates new action that creates a webhook for a repository on GitHub. From 2cc750d36766a43c277760af681a28e373867243 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Fri, 19 Apr 2024 00:55:19 -0600 Subject: [PATCH 125/567] feat: integration support for harness Signed-off-by: Calvin Lee --- .changeset/empty-beers-relax.md | 5 + .changeset/tasty-rats-explain.md | 5 + .../config/vocabularies/Backstage/accept.txt | 2 + docs/integrations/harness/locations.md | 33 +++ microsite/sidebars.json | 5 + packages/backend-common/api-report.md | 26 +++ .../src/reading/HarnessCodeUrlReader.test.ts | 204 ++++++++++++++++++ .../src/reading/HarnessUrlReader.ts | 124 +++++++++++ .../backend-common/src/reading/UrlReaders.ts | 3 +- packages/backend-common/src/reading/index.ts | 1 + packages/integration/api-report.md | 55 ++++- packages/integration/config.d.ts | 24 +++ .../integration/src/ScmIntegrations.test.ts | 9 + packages/integration/src/ScmIntegrations.ts | 7 + .../src/harness/HarnessIntegration.test.ts | 128 +++++++++++ .../src/harness/HarnessIntegration.ts | 58 +++++ .../integration/src/harness/config.test.ts | 108 ++++++++++ packages/integration/src/harness/config.ts | 78 +++++++ packages/integration/src/harness/core.test.ts | 95 ++++++++ packages/integration/src/harness/core.ts | 135 ++++++++++++ packages/integration/src/harness/index.ts | 19 ++ packages/integration/src/index.ts | 1 + packages/integration/src/registry.ts | 2 + 23 files changed, 1125 insertions(+), 2 deletions(-) create mode 100644 .changeset/empty-beers-relax.md create mode 100644 .changeset/tasty-rats-explain.md create mode 100644 docs/integrations/harness/locations.md create mode 100644 packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts create mode 100644 packages/backend-common/src/reading/HarnessUrlReader.ts create mode 100644 packages/integration/src/harness/HarnessIntegration.test.ts create mode 100644 packages/integration/src/harness/HarnessIntegration.ts create mode 100644 packages/integration/src/harness/config.test.ts create mode 100644 packages/integration/src/harness/config.ts create mode 100644 packages/integration/src/harness/core.test.ts create mode 100644 packages/integration/src/harness/core.ts create mode 100644 packages/integration/src/harness/index.ts diff --git a/.changeset/empty-beers-relax.md b/.changeset/empty-beers-relax.md new file mode 100644 index 0000000000..526f88c1c7 --- /dev/null +++ b/.changeset/empty-beers-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +This patch adds HarnessURLReader to the available classes. It currently only reads single files via Harness codes public repo api. diff --git a/.changeset/tasty-rats-explain.md b/.changeset/tasty-rats-explain.md new file mode 100644 index 0000000000..aee8057915 --- /dev/null +++ b/.changeset/tasty-rats-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +This patch brings Harness Code as a valid integration via the ScmIntgration interface. It adds harness code to the relevant static properties ( get integration by name, get integration by type) for plugs to be able to reference the same harness code server diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 026e5a9b18..2e42ff7998 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -152,6 +152,8 @@ graphviz Hackathons haproxy hardcoded +Harness +harness Helidon Henneke Heroku diff --git a/docs/integrations/harness/locations.md b/docs/integrations/harness/locations.md new file mode 100644 index 0000000000..8911d7918c --- /dev/null +++ b/docs/integrations/harness/locations.md @@ -0,0 +1,33 @@ +--- +id: locations +title: Harness Locations +sidebar_label: Locations +description: Integrating source code stored in Harness Code into the Backstage catalog +--- + +The Harness Code integration supports loading catalog entities from a hosted repository. Entities can be added to +[static catalog configuration](../../features/software-catalog/configuration.md), +registered with the +[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) +plugin. + +## Configuration + +To use this integration, add configuration to your root `app-config.yaml`: + +```yaml +integrations: + harness: + - host: app.harness.io + token: ${HARNESS_CODE_BEARER_TOKEN} +``` + +Directly under the `harnessCode` key is a list of provider configurations, where you +can list the Gitea instances you want to be able to fetch +data from. Each entry is a structure with up to four elements: + +- `host`: The host of the Harness Code instance that you want to match on. +- `baseUrl` (optional): Needed if the Harness Code instance is not reachable at + the base of the `host` option (e.g. `https://app.harness.io`). This is the address that you would open in a browser. +- `username` (optional): The gitea username to use in API requests. +- `token` (optional): The password or api token to authenticate with. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 32863c61db..8291549153 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -238,6 +238,11 @@ "label": "Gitea", "items": ["integrations/gitea/locations"] }, + { + "type": "subcategory", + "label": "Harness", + "ids": ["integrations/harness/locations"] + }, { "type": "category", "label": "Google GCS", diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index f9e0d76aab..37bfda9861 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -30,6 +30,7 @@ import { GiteaIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GithubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; +import { HarnessIntegration } from '@backstage/integration'; import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; @@ -530,6 +531,31 @@ export class GitlabUrlReader implements UrlReader { toString(): string; } +// @public +export class HarnessUrlReader implements UrlReader { + constructor(integration: HarnessIntegration); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(): Promise; + // (undocumented) + readUrl(url: string, options?: ReadUrlOptions): Promise; + // (undocumented) + search(): Promise; + // (undocumented) + toString(): string; +} + +// @public +export type HarnessIntegrationConfig = { + host: string; + baseUrl?: string; + username?: string; + token?: string; +}; + // @public export const HostDiscovery: typeof HostDiscovery_2; diff --git a/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts b/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts new file mode 100644 index 0000000000..3e8b70a6aa --- /dev/null +++ b/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts @@ -0,0 +1,204 @@ +/* + * 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 { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { HarnessIntegration, readHarnessConfig } from '@backstage/integration'; +import { JsonObject } from '@backstage/types'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { getVoidLogger } from '../logging'; +import { UrlReaderPredicateTuple } from './types'; +import { DefaultReadTreeResponseFactory } from './tree'; +import getRawBody from 'raw-body'; +import { HarnessUrlReader } from './HarnessUrlReader'; +import { NotFoundError } from '@backstage/errors'; + +const treeResponseFactory = DefaultReadTreeResponseFactory.create({ + config: new ConfigReader({}), +}); + +jest.mock('../scm', () => ({ + Git: { + fromAuth: () => ({ + clone: jest.fn(() => Promise.resolve({})), + }), + }, +})); + +const harnessProcessor = new HarnessUrlReader( + new HarnessIntegration( + readHarnessConfig( + new ConfigReader({ + host: 'app.harness.io', + }), + ), + ), +); + +const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { + return HarnessUrlReader.factory({ + config: new ConfigReader(config), + logger: getVoidLogger(), + treeResponseFactory, + }); +}; + +describe('HarnessUrlReader', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + afterAll(() => { + jest.clearAllMocks(); + }); + + describe('reader factory', () => { + it('creates a reader.', () => { + const readers = createReader({ + integrations: { + harness: [{ host: 'app.harness.io' }], + }, + }); + expect(readers).toHaveLength(1); + }); + + it('should not create a default entry.', () => { + const readers = createReader({ + integrations: {}, + }); + expect(readers).toHaveLength(0); + }); + }); + + describe('predicates', () => { + it('returns true for the configured host', () => { + const readers = createReader({ + integrations: { + harness: [{ host: 'app.harness.io' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://app.harness.io/path'))).toBe(true); + }); + + it('returns false for a different host.', () => { + const readers = createReader({ + integrations: { + harness: [{ host: 'app.harness.io' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://github.com/path'))).toBe(false); + }); + }); + + describe('readUrl', () => { + const responseBuffer = Buffer.from('Apache License'); + const harnessApiResponse = (content: any) => { + return JSON.stringify({ + encoding: 'base64', + content: Buffer.from(content).toString('base64'), + }); + }; + + it.skip('should be able to read file contents as buffer', async () => { + worker.use( + rest.get( + 'https://app.harness.io/api/v1/repos/owner/project/contents/LICENSE', + (req, res, ctx) => { + // Test utils prefers matching URL directly but it is part of Gitea's API + if (req.url.searchParams.get('ref') === 'branch2') { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await harnessProcessor.readUrl( + 'https://app.harness.io/owner/project/src/branch/branch2/LICENSE', + ); + const buffer = await result.buffer(); + expect(buffer.toString()).toBe(responseBuffer.toString()); + }); + + it.skip('should be able to read file contents as stream', async () => { + worker.use( + rest.get( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.txt', + (req, res, ctx) => { + if (req.url.searchParams.get('ref') === 'refMain') { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await harnessProcessor.readUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.TXT', + ); + const fromStream = await getRawBody(result.stream!()); + expect(fromStream.toString()).toBe(responseBuffer.toString()); + }); + + it.skip('should raise NotFoundError on 404.', async () => { + worker.use( + rest.get( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + (_, res, ctx) => { + return res(ctx.status(404, 'File not found.')); + }, + ), + ); + + await expect( + harnessProcessor.readUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + ), + ).rejects.toThrow(NotFoundError); + }); + + it.skip('should throw an error on non 404 errors.', async () => { + worker.use( + rest.get( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + (_, res, ctx) => { + return res(ctx.status(500, 'Error!!!')); + }, + ), + ); + + await expect( + harnessProcessor.readUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + ), + ).rejects.toThrow( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Error!!!', + ); + }); + }); +}); diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts new file mode 100644 index 0000000000..fd01e4f8a2 --- /dev/null +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -0,0 +1,124 @@ +/* + * Copyright 2025 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + getHarnessRequestOptions, + getHarnessFileContentsUrl, + HarnessIntegration, + ScmIntegrations, +} from '@backstage/integration'; +import { ReadUrlOptions, ReadUrlResponse } from './types'; +import { + ReaderFactory, + ReadTreeResponse, + SearchResponse, + UrlReader, +} from './types'; +import fetch, { Response } from 'node-fetch'; +import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; +import { + AuthenticationError, + NotFoundError, + NotModifiedError, +} from '@backstage/errors'; +import { Readable } from 'stream'; + +/** + * Implements a {@link UrlReader} for the Harness code v1 api. + * + * @public + */ +export class HarnessUrlReader implements UrlReader { + static factory: ReaderFactory = ({ config }) => { + return ScmIntegrations.fromConfig(config) + .harness.list() + .map(integration => { + const reader = new HarnessUrlReader(integration); + const predicate = (url: URL) => { + return url.host === integration.config.host; + }; + return { reader, predicate }; + }); + }; + + constructor(private readonly integration: HarnessIntegration) {} + + async read(url: string): Promise { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { + let response: Response; + const blobUrl = getHarnessFileContentsUrl(this.integration.config, url); + + try { + response = await fetch(blobUrl, { + method: 'GET', + ...getHarnessRequestOptions(this.integration.config), + signal: options?.signal as any, + }); + } catch (e) { + throw new Error(`Unable to read ${blobUrl}, ${e}`); + } + + if (response.ok) { + // Harness Code returns an object with the file contents encoded, not the file itself + const jsonResponse = await response.json(); + if (jsonResponse?.content?.encoding === 'base64') { + return ReadUrlResponseFactory.fromReadable( + Readable.from(Buffer.from(jsonResponse?.content?.data, 'base64')), + { + etag: response.headers.get('ETag') ?? undefined, + }, + ); + } + + throw new Error(`Unknown encoding: ${jsonResponse?.content?.encoding}`); + } + + const message = `${url} x ${blobUrl}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + throw new NotFoundError(message); + } + + if (response.status === 304) { + throw new NotModifiedError(); + } + + if (response.status === 403) { + throw new AuthenticationError(); + } + + throw new Error(message); + } + + readTree(): Promise { + throw new Error('HarnessUrlReader readTree not implemented.'); + } + search(): Promise { + throw new Error('HarnessUrlReader search not implemented.'); + } + + toString() { + const { host } = this.integration.config; + return `harness{host=${host},authed=${Boolean( + this.integration.config.token, + )}}`; + } +} diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index e987eb9186..7aaaf120b4 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -31,6 +31,7 @@ import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; import { AwsS3UrlReader } from './AwsS3UrlReader'; import { GiteaUrlReader } from './GiteaUrlReader'; import { AwsCodeCommitUrlReader } from './AwsCodeCommitUrlReader'; +import { HarnessUrlReader } from './HarnessUrlReader'; /** * Creation options for {@link @backstage/backend-plugin-api#UrlReaderService}. @@ -61,7 +62,6 @@ export class UrlReaders { const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config, }); - for (const factory of factories ?? []) { const tuples = factory({ config, logger: logger, treeResponseFactory }); @@ -94,6 +94,7 @@ export class UrlReaders { GiteaUrlReader.factory, GitlabUrlReader.factory, GoogleGcsUrlReader.factory, + HarnessUrlReader.factory, AwsS3UrlReader.factory, AwsCodeCommitUrlReader.factory, FetchUrlReader.factory, diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index 21e82da9b5..a99bb6dda1 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -22,6 +22,7 @@ export { GerritUrlReader } from './GerritUrlReader'; export { GithubUrlReader } from './GithubUrlReader'; export { GitlabUrlReader } from './GitlabUrlReader'; export { GiteaUrlReader } from './GiteaUrlReader'; +export { HarnessUrlReader } from './HarnessUrlReader'; export { AwsS3UrlReader } from './AwsS3UrlReader'; export { FetchUrlReader } from './FetchUrlReader'; export { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 6b691dff80..a693c1314c 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -513,6 +513,19 @@ export function getGitLabRequestOptions(config: GitLabIntegrationConfig): { headers: Record; }; +// @public +export function getHarnessFileContentsUrl( + config: HarnessIntegrationConfig, + url: string, +): string; + +// @public +export function getHarnessRequestOptions( + config: HarnessIntegrationConfig, +): { + headers?: Record; +}; + // @public export class GiteaIntegration implements ScmIntegration { constructor(config: GiteaIntegrationConfig); @@ -539,7 +552,7 @@ export type GiteaIntegrationConfig = { host: string; baseUrl?: string; username?: string; - password?: string; + token?: string; }; // @public @@ -674,6 +687,35 @@ export type GoogleGcsIntegrationConfig = { privateKey?: string; }; +// @public +export class HarnessIntegration implements ScmIntegration { + constructor(config: HarnessIntegrationConfig); + // (undocumented) + readonly config: HarnessIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory; + // (undocumented) + resolveEditUrl(url: string): string; + // (undocumented) + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string; + // (undocumented) + get title(): string; + // (undocumented) + get type(): string; +} + +// @public +export type HarnessIntegrationConfig = { + host: string; + baseUrl?: string; + username?: string; + token?: string; +}; + // @public export interface IntegrationsByType { // (undocumented) @@ -696,6 +738,8 @@ export interface IntegrationsByType { github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; + // (undocumented) + harness: ScmIntegrationsGroup; } // @public @@ -839,6 +883,11 @@ export function readGoogleGcsIntegrationConfig( config: Config, ): GoogleGcsIntegrationConfig; +// @public +export function readHarnessConfig( + config: Config, +): HarnessIntegrationConfig; + // @public @deprecated (undocumented) export const replaceGitHubUrlType: typeof replaceGithubUrlType; @@ -889,6 +938,8 @@ export interface ScmIntegrationRegistry github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; + // (undocumented) + harness: ScmIntegrationsGroup; resolveEditUrl(url: string): string; resolveUrl(options: { url: string; @@ -927,6 +978,8 @@ export class ScmIntegrations implements ScmIntegrationRegistry { // (undocumented) get gitlab(): ScmIntegrationsGroup; // (undocumented) + get harness(): ScmIntegrationsGroup; + // (undocumented) list(): ScmIntegration[]; // (undocumented) resolveEditUrl(url: string): string; diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 44303bf25c..01f1f44d73 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -345,5 +345,29 @@ export interface Config { */ password?: string; }>; + /** Integration configuration for Harness Code */ + harness?: Array<{ + /** + * The hostname of the given Harness Code instance + * @visibility frontend + */ + host: string; + /** + * The base url for the Gitea instance. + * @visibility frontend + */ + baseUrl?: string; + + /** + * The username to use for authenticated requests. + * @visibility secret + */ + username?: string; + /** + * Harness Code token used to authenticate requests. This can be either a generated access token. + * @visibility secret + */ + token?: string; + }>; }; } diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index acf602e38d..c758816c03 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -38,6 +38,7 @@ import { ScmIntegrations } from './ScmIntegrations'; import { GiteaIntegration, GiteaIntegrationConfig } from './gitea'; import { AwsCodeCommitIntegration } from './awsCodeCommit/AwsCodeCommitIntegration'; import { AwsCodeCommitIntegrationConfig } from './awsCodeCommit'; +import { HarnessIntegration, HarnessIntegrationConfig } from './harness'; describe('ScmIntegrations', () => { const awsS3 = new AwsS3Integration({ @@ -80,6 +81,10 @@ describe('ScmIntegrations', () => { host: 'gitea.local', } as GiteaIntegrationConfig); + const harness = new HarnessIntegration({ + host: 'harness.local', + } as HarnessIntegrationConfig); + const i = new ScmIntegrations({ awsS3: basicIntegrations([awsS3], item => item.config.host), awsCodeCommit: basicIntegrations([awsCodeCommit], item => item.config.host), @@ -94,6 +99,7 @@ describe('ScmIntegrations', () => { github: basicIntegrations([github], item => item.config.host), gitlab: basicIntegrations([gitlab], item => item.config.host), gitea: basicIntegrations([gitea], item => item.config.host), + harness: basicIntegrations([harness], item => item.config.host), }); it('can get the specifics', () => { @@ -113,6 +119,7 @@ describe('ScmIntegrations', () => { expect(i.github.byUrl('https://github.local')).toBe(github); expect(i.gitlab.byUrl('https://gitlab.local')).toBe(gitlab); expect(i.gitea.byUrl('https://gitea.local')).toBe(gitea); + expect(i.harness.byUrl('https://harness.local')).toBe(harness); }); it('can list', () => { @@ -128,6 +135,7 @@ describe('ScmIntegrations', () => { github, gitlab, gitea, + harness, ]), ); }); @@ -143,6 +151,7 @@ describe('ScmIntegrations', () => { expect(i.byUrl('https://github.local')).toBe(github); expect(i.byUrl('https://gitlab.local')).toBe(gitlab); expect(i.byUrl('https://gitea.local')).toBe(gitea); + expect(i.byUrl('https://harness.local')).toBe(harness); expect(i.byHost('awss3.local')).toBe(awsS3); expect(i.byHost('awscodecommit.local')).toBe(awsCodeCommit); diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index 6a1faaa75b..e0d430d834 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -28,6 +28,7 @@ import { defaultScmResolveUrl } from './helpers'; import { ScmIntegration, ScmIntegrationsGroup } from './types'; import { ScmIntegrationRegistry } from './registry'; import { GiteaIntegration } from './gitea'; +import { HarnessIntegration } from './harness/HarnessIntegration'; /** * The set of supported integrations. @@ -48,6 +49,7 @@ export interface IntegrationsByType { github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; gitea: ScmIntegrationsGroup; + harness: ScmIntegrationsGroup; } /** @@ -70,6 +72,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { github: GithubIntegration.factory({ config }), gitlab: GitLabIntegration.factory({ config }), gitea: GiteaIntegration.factory({ config }), + harness: HarnessIntegration.factory({ config }), }); } @@ -120,6 +123,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gitea; } + get harness(): ScmIntegrationsGroup { + return this.byType.harness; + } + list(): ScmIntegration[] { return Object.values(this.byType).flatMap( i => i.list() as ScmIntegration[], diff --git a/packages/integration/src/harness/HarnessIntegration.test.ts b/packages/integration/src/harness/HarnessIntegration.test.ts new file mode 100644 index 0000000000..9014c0bb79 --- /dev/null +++ b/packages/integration/src/harness/HarnessIntegration.test.ts @@ -0,0 +1,128 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { HarnessIntegration } from './HarnessIntegration'; + +describe('HarnessIntegration', () => { + it('has a working factory', () => { + const integrations = HarnessIntegration.factory({ + config: new ConfigReader({ + integrations: { + harness: [ + { + host: 'app.harness.io', + username: 'git', + baseUrl: 'https://app.harness.io/route', + token: '1234', + }, + ], + }, + }), + }); + expect(integrations.list().length).toBe(1); + expect(integrations.list()[0].config.host).toBe('app.harness.io'); + expect(integrations.list()[0].config.baseUrl).toBe( + 'https://app.harness.io/route', + ); + }); + + it('returns the basics', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + expect(integration.type).toBe('harness'); + expect(integration.title).toBe('app.harness.io'); + }); + + describe('resolveUrl', () => { + it('works for valid urls, ignoring line number', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: 'https://app.harness.io/catalog-info.yaml', + base: 'https://app.harness.io/catalog-info.yaml', + lineNumber: 9, + }), + ).toBe('https://app.harness.io/catalog-info.yaml'); + }); + + it('handles line numbers', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: '', + base: 'https://app.harness.io/catalog-info.yaml#4', + lineNumber: 9, + }), + ).toBe('https://app.harness.io/catalog-info.yaml#L9'); + }); + }); + + describe('resolves with a relative url', () => { + it('works for valid urls', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: './skeleton', + base: 'https://app.harness.io/git/plugins/repo/+/refs/heads/master/template.yaml', + }), + ).toBe( + 'https://app.harness.io/git/plugins/repo/+/refs/heads/master/skeleton', + ); + }); + }); + + describe('resolves with an absolute url', () => { + it('works for valid urls', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: '/catalog-info.yaml', + base: 'https://app.harness.io/git/repo/+/refs/heads/master/', + }), + ).toBe( + 'https://app.harness.io/git/repo/+/refs/heads/master/catalog-info.yaml', + ); + }); + }); + + it('resolve edit URL', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveEditUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/edit/refMain/~/all-apis.yaml', + ), + ).toBe( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/edit/all-apis.yaml', + ); + }); +}); diff --git a/packages/integration/src/harness/HarnessIntegration.ts b/packages/integration/src/harness/HarnessIntegration.ts new file mode 100644 index 0000000000..5d1b274149 --- /dev/null +++ b/packages/integration/src/harness/HarnessIntegration.ts @@ -0,0 +1,58 @@ +/* + * 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 { basicIntegrations, defaultScmResolveUrl } from '../helpers'; +import { ScmIntegration, ScmIntegrationsFactory } from '../types'; +import { HarnessIntegrationConfig, readHarnessConfig } from './config'; +import { getHarnessEditContentsUrl } from './core'; + +/** + * A Harness Code based integration. + * + * @public + */ +export class HarnessIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory = ({ config }) => { + const configs = config.getOptionalConfigArray('integrations.harness') ?? []; + const harnessConfigs = configs.map(c => readHarnessConfig(c)); + + return basicIntegrations( + harnessConfigs.map(c => new HarnessIntegration(c)), + (harness: HarnessIntegration) => harness.config.host, + ); + }; + + constructor(readonly config: HarnessIntegrationConfig) {} + + get type(): string { + return 'harness'; + } + + get title(): string { + return this.config.host; + } + + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string { + return defaultScmResolveUrl(options); + } + + resolveEditUrl(url: string): string { + return getHarnessEditContentsUrl(this.config, url); + } +} diff --git a/packages/integration/src/harness/config.test.ts b/packages/integration/src/harness/config.test.ts new file mode 100644 index 0000000000..6482e89de8 --- /dev/null +++ b/packages/integration/src/harness/config.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config, ConfigReader } from '@backstage/config'; +import { loadConfigSchema } from '@backstage/config-loader'; +import { HarnessIntegrationConfig, readHarnessConfig } from './config'; + +describe('readHarnessConfig', () => { + function buildConfig(data: Partial): Config { + return new ConfigReader(data); + } + + async function buildFrontendConfig( + data: Partial, + ): Promise { + const fullSchema = await loadConfigSchema({ + dependencies: ['@backstage/integration'], + }); + const serializedSchema = fullSchema.serialize() as { + schemas: { value: { properties?: { integrations?: object } } }[]; + }; + const schema = await loadConfigSchema({ + serialized: { + ...serializedSchema, // only include schemas that apply to integrations + schemas: serializedSchema.schemas.filter( + s => s.value?.properties?.integrations, + ), + }, + }); + const processed = schema.process( + [{ data: { integrations: { harness: [data] } }, context: 'app' }], + { visibility: ['frontend'] }, + ); + return new ConfigReader((processed[0].data as any).integrations.harness[0]); + } + + it('reads all values', () => { + const output = readHarnessConfig( + buildConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + token: 'p', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + token: 'p', + }); + }); + + it('can create a default value if the API base URL is missing', () => { + const output = readHarnessConfig( + buildConfig({ + host: 'a.com', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com', + username: undefined, + token: undefined, + }); + }); + + it('rejects funky configs', () => { + const valid: any = { + host: 'a.com', + }; + expect(() => readHarnessConfig(buildConfig({ ...valid, host: 2 }))).toThrow( + /host/, + ); + expect(() => + readHarnessConfig(buildConfig({ ...valid, baseUrl: 2 })), + ).toThrow(/baseUrl/); + }); + + it('works on the frontend', async () => { + expect( + readHarnessConfig( + await buildFrontendConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route', + username: 'u', + token: 'p', + }), + ), + ).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route', + }); + }); +}); diff --git a/packages/integration/src/harness/config.ts b/packages/integration/src/harness/config.ts new file mode 100644 index 0000000000..75482a9b39 --- /dev/null +++ b/packages/integration/src/harness/config.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { trimEnd } from 'lodash'; +import { isValidHost } from '../helpers'; + +/** + * The configuration for a single Gitea integration. + * + * @public + */ +export type HarnessIntegrationConfig = { + /** + * The host of the target that this matches on, e.g. "app.harness.io" + */ + host: string; + /** + * The optional base URL of the Harness code instance. It is assumed that https + * is used and that the base path is "/" on the host. If that is not the + * case set the complete base url to the Harness code instance, e.g. + * "https://harnesscode.website.com/". This is the url that you would open + * in a browser. + */ + baseUrl?: string; + /** + * The username to use for requests to harness code. + */ + username?: string; + + /** + * The password or http token to use for authentication. + */ + token?: string; +}; + +/** + * Parses a location config block for use in HarnessIntegration + * + * @public + */ +export function readHarnessConfig(config: Config): HarnessIntegrationConfig { + const host = config.getString('host'); + let baseUrl = config.getOptionalString('baseUrl'); + const username = config.getOptionalString('username'); + const token = config.getOptionalString('token'); + if (!isValidHost(host)) { + throw new Error( + `Invalid Harness Code integration config, '${host}' is not a valid host`, + ); + } + + if (baseUrl) { + baseUrl = trimEnd(baseUrl, '/'); + } else { + baseUrl = `https://${host}`; + } + + return { + host, + baseUrl, + username, + token, + }; +} diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts new file mode 100644 index 0000000000..6502fd3e48 --- /dev/null +++ b/packages/integration/src/harness/core.test.ts @@ -0,0 +1,95 @@ +/* + * 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 { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { HarnessIntegrationConfig } from './config'; +import { + getHarnessEditContentsUrl, + getHarnessFileContentsUrl, + getHarnessRequestOptions, +} from './core'; + +describe('Harness code core', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + describe('getHarnessFileContentsUrl', () => { + it('can create an url from arguments', () => { + const config: HarnessIntegrationConfig = { + host: 'app.harness.io', + }; + expect( + getHarnessFileContentsUrl( + config, + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + ), + ).toEqual( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain', + ); + }); + }); + + describe('getHarnessEditContentsUrl', () => { + it('can create an url from arguments', () => { + const config: HarnessIntegrationConfig = { + host: 'app.harness.io', + }; + expect( + getHarnessEditContentsUrl( + config, + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/edit/refMain/~/all-apis.yaml', + ), + ).toEqual( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/edit/all-apis.yaml', + ); + }); + }); + + describe('getGerritRequestOptions', () => { + it('adds token header when only a token is specified', () => { + const authRequest: HarnessIntegrationConfig = { + host: 'gerrit.com', + token: 'P', + }; + const anonymousRequest: HarnessIntegrationConfig = { + host: 'gerrit.com', + }; + expect( + (getHarnessRequestOptions(authRequest).headers as any).Authorization, + ).toEqual('Bearer P'); + expect( + getHarnessRequestOptions(anonymousRequest).headers, + ).toBeUndefined(); + }); + + it('adds basic auth when username and token are specified', () => { + const authRequest: HarnessIntegrationConfig = { + host: 'gerrit.com', + username: 'username', + token: 'P', + }; + + const basicAuthentication = `basic ${Buffer.from( + `${authRequest.username}:${authRequest.token}`, + ).toString('base64')}`; + + expect( + (getHarnessRequestOptions(authRequest).headers as any).Authorization, + ).toEqual(basicAuthentication); + }); + }); +}); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts new file mode 100644 index 0000000000..0c4fb01970 --- /dev/null +++ b/packages/integration/src/harness/core.ts @@ -0,0 +1,135 @@ +/* + * 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 { HarnessIntegrationConfig } from './config'; + +/** + * Given a URL pointing to a file, returns a URL + * for editing the contents of the data. + * + * @remarks + * + * Converts + * from: https://app.harness.io/a/b/src/branchname/path/to/c.yaml + * or: https://app.harness.io/a/b/_edit/branchname/path/to/c.yaml + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getHarnessEditContentsUrl( + config: HarnessIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [ + _blank, + _ng, + _account, + accountId, + _module, + _moduleName, + _org, + orgName, + _projects, + projectName, + _repos, + repoName, + _files, + _ref, + _branch, + ...path + ] = url.replace(baseUrl, '').split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/edit/${pathWithoutSlash}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Given a URL pointing to a file, returns an api URL + * for fetching the contents of the data. + * + * @remarks + * + * Converts + * from: https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml + * to: https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getHarnessFileContentsUrl( + config: HarnessIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [ + _blank, + _ng, + _account, + accountId, + _module, + _moduleName, + _org, + orgName, + _projects, + projectName, + _repos, + repoName, + _files, + ref, + _branch, + ...path + ] = url.replace(baseUrl, '').split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${ref}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Return request headers for a Harness Code provider. + * + * @param config - A Harness Code provider config + * @public + */ +export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { + headers?: Record; +} { + const headers: Record = {}; + const { username, token } = config; + + if (!token) { + return headers; + } + + if (username) { + headers.Authorization = `basic ${Buffer.from( + `${username}:${token}`, + ).toString('base64')}`; + } else { + headers.Authorization = `Bearer ${token}`; + } + + return { + headers, + }; +} diff --git a/packages/integration/src/harness/index.ts b/packages/integration/src/harness/index.ts new file mode 100644 index 0000000000..264df657f0 --- /dev/null +++ b/packages/integration/src/harness/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { HarnessIntegration } from './HarnessIntegration'; +export { getHarnessRequestOptions, getHarnessFileContentsUrl } from './core'; +export { readHarnessConfig } from './config'; +export type { HarnessIntegrationConfig } from './config'; diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index b20477ee6a..32c573abef 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -31,6 +31,7 @@ export * from './gitea'; export * from './github'; export * from './gitlab'; export * from './googleGcs'; +export * from './harness'; export { defaultScmResolveUrl } from './helpers'; export { ScmIntegrations } from './ScmIntegrations'; export type { IntegrationsByType } from './ScmIntegrations'; diff --git a/packages/integration/src/registry.ts b/packages/integration/src/registry.ts index 00706acc1c..7e4b34cc34 100644 --- a/packages/integration/src/registry.ts +++ b/packages/integration/src/registry.ts @@ -25,6 +25,7 @@ import { GerritIntegration } from './gerrit/GerritIntegration'; import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { GiteaIntegration } from './gitea/GiteaIntegration'; +import { HarnessIntegration } from './harness/HarnessIntegration'; /** * Holds all registered SCM integrations, of all types. @@ -46,6 +47,7 @@ export interface ScmIntegrationRegistry github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; gitea: ScmIntegrationsGroup; + harness: ScmIntegrationsGroup; /** * Resolves an absolute or relative URL in relation to a base URL. * From 4750bf66223a0befc33ab8226c7036a5f0d2e898 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Sun, 21 Apr 2024 19:39:59 -0600 Subject: [PATCH 126/567] Update .changeset/empty-beers-relax.md Co-authored-by: Himanshu Mishra Signed-off-by: Calvin Lee --- .changeset/empty-beers-relax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/empty-beers-relax.md b/.changeset/empty-beers-relax.md index 526f88c1c7..a285b9e7b4 100644 --- a/.changeset/empty-beers-relax.md +++ b/.changeset/empty-beers-relax.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -This patch adds HarnessURLReader to the available classes. It currently only reads single files via Harness codes public repo api. +This patch adds HarnessURLReader. It only supports readUrl for now. readTree and search will be implemented next. From d422716946e6bd1ca2072f21b510add272eb0b59 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Sun, 21 Apr 2024 20:31:45 -0600 Subject: [PATCH 127/567] integration support for harness p2-comments Signed-off-by: Calvin Lee --- docs/integrations/harness/locations.md | 12 +++---- .../src/reading/HarnessUrlReader.ts | 2 +- packages/integration/api-report.md | 5 ++- .../src/harness/HarnessIntegration.test.ts | 5 --- .../integration/src/harness/config.test.ts | 11 ++----- packages/integration/src/harness/config.ts | 32 ++++++------------- packages/integration/src/harness/core.test.ts | 12 +++---- packages/integration/src/harness/core.ts | 17 ++++------ 8 files changed, 31 insertions(+), 65 deletions(-) diff --git a/docs/integrations/harness/locations.md b/docs/integrations/harness/locations.md index 8911d7918c..a3d0e2cfd4 100644 --- a/docs/integrations/harness/locations.md +++ b/docs/integrations/harness/locations.md @@ -20,14 +20,14 @@ integrations: harness: - host: app.harness.io token: ${HARNESS_CODE_BEARER_TOKEN} + apiKey: ${HARNESS_CODE_APIKEY} ``` -Directly under the `harnessCode` key is a list of provider configurations, where you -can list the Gitea instances you want to be able to fetch -data from. Each entry is a structure with up to four elements: +Directly under the `harness` key is a list of provider configurations, where you +can list the Harness instances you want to be able to fetch + +check out https://developer.harness.io/docs/platform/automation/api/add-and-manage-api-keys/ for more information - `host`: The host of the Harness Code instance that you want to match on. -- `baseUrl` (optional): Needed if the Harness Code instance is not reachable at - the base of the `host` option (e.g. `https://app.harness.io`). This is the address that you would open in a browser. -- `username` (optional): The gitea username to use in API requests. - `token` (optional): The password or api token to authenticate with. +- `apiKey` (optional): The apiKey to authenticate with. diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index fd01e4f8a2..95114ae9d6 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -118,7 +118,7 @@ export class HarnessUrlReader implements UrlReader { toString() { const { host } = this.integration.config; return `harness{host=${host},authed=${Boolean( - this.integration.config.token, + this.integration.config.token || this.integration.config.apiKey, )}}`; } } diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index a693c1314c..b40b66b0b6 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -552,7 +552,7 @@ export type GiteaIntegrationConfig = { host: string; baseUrl?: string; username?: string; - token?: string; + password?: string; }; // @public @@ -711,8 +711,7 @@ export class HarnessIntegration implements ScmIntegration { // @public export type HarnessIntegrationConfig = { host: string; - baseUrl?: string; - username?: string; + apiKey?: string; token?: string; }; diff --git a/packages/integration/src/harness/HarnessIntegration.test.ts b/packages/integration/src/harness/HarnessIntegration.test.ts index 9014c0bb79..2d85204f16 100644 --- a/packages/integration/src/harness/HarnessIntegration.test.ts +++ b/packages/integration/src/harness/HarnessIntegration.test.ts @@ -25,8 +25,6 @@ describe('HarnessIntegration', () => { harness: [ { host: 'app.harness.io', - username: 'git', - baseUrl: 'https://app.harness.io/route', token: '1234', }, ], @@ -35,9 +33,6 @@ describe('HarnessIntegration', () => { }); expect(integrations.list().length).toBe(1); expect(integrations.list()[0].config.host).toBe('app.harness.io'); - expect(integrations.list()[0].config.baseUrl).toBe( - 'https://app.harness.io/route', - ); }); it('returns the basics', () => { diff --git a/packages/integration/src/harness/config.test.ts b/packages/integration/src/harness/config.test.ts index 6482e89de8..4f6d8496e3 100644 --- a/packages/integration/src/harness/config.test.ts +++ b/packages/integration/src/harness/config.test.ts @@ -51,16 +51,14 @@ describe('readHarnessConfig', () => { const output = readHarnessConfig( buildConfig({ host: 'a.com', - baseUrl: 'https://a.com/route/api', - username: 'u', token: 'p', + apiKey: 'a', }), ); expect(output).toEqual({ host: 'a.com', - baseUrl: 'https://a.com/route/api', - username: 'u', token: 'p', + apiKey: 'a', }); }); @@ -72,8 +70,6 @@ describe('readHarnessConfig', () => { ); expect(output).toEqual({ host: 'a.com', - baseUrl: 'https://a.com', - username: undefined, token: undefined, }); }); @@ -95,14 +91,11 @@ describe('readHarnessConfig', () => { readHarnessConfig( await buildFrontendConfig({ host: 'a.com', - baseUrl: 'https://a.com/route', - username: 'u', token: 'p', }), ), ).toEqual({ host: 'a.com', - baseUrl: 'https://a.com/route', }); }); }); diff --git a/packages/integration/src/harness/config.ts b/packages/integration/src/harness/config.ts index 75482a9b39..748cd141c5 100644 --- a/packages/integration/src/harness/config.ts +++ b/packages/integration/src/harness/config.ts @@ -15,11 +15,10 @@ */ import { Config } from '@backstage/config'; -import { trimEnd } from 'lodash'; import { isValidHost } from '../helpers'; /** - * The configuration for a single Gitea integration. + * The configuration for a single Harness integration. * * @public */ @@ -28,23 +27,14 @@ export type HarnessIntegrationConfig = { * The host of the target that this matches on, e.g. "app.harness.io" */ host: string; - /** - * The optional base URL of the Harness code instance. It is assumed that https - * is used and that the base path is "/" on the host. If that is not the - * case set the complete base url to the Harness code instance, e.g. - * "https://harnesscode.website.com/". This is the url that you would open - * in a browser. - */ - baseUrl?: string; - /** - * The username to use for requests to harness code. - */ - username?: string; - /** * The password or http token to use for authentication. */ token?: string; + /** + * The API key to use for authentication. + */ + apiKey?: string; }; /** @@ -55,24 +45,20 @@ export type HarnessIntegrationConfig = { export function readHarnessConfig(config: Config): HarnessIntegrationConfig { const host = config.getString('host'); let baseUrl = config.getOptionalString('baseUrl'); - const username = config.getOptionalString('username'); const token = config.getOptionalString('token'); + const apiKey = config.getOptionalString('apiKey'); + if (!isValidHost(host)) { throw new Error( `Invalid Harness Code integration config, '${host}' is not a valid host`, ); } - if (baseUrl) { - baseUrl = trimEnd(baseUrl, '/'); - } else { - baseUrl = `https://${host}`; - } + baseUrl = `https://${host}`; return { host, - baseUrl, - username, + apiKey, token, }; } diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts index 6502fd3e48..2a214cc06f 100644 --- a/packages/integration/src/harness/core.test.ts +++ b/packages/integration/src/harness/core.test.ts @@ -76,20 +76,16 @@ describe('Harness code core', () => { ).toBeUndefined(); }); - it('adds basic auth when username and token are specified', () => { + it('adds basic auth when apikey and token are specified', () => { const authRequest: HarnessIntegrationConfig = { host: 'gerrit.com', - username: 'username', token: 'P', + apiKey: 'a', }; - const basicAuthentication = `basic ${Buffer.from( - `${authRequest.username}:${authRequest.token}`, - ).toString('base64')}`; - expect( - (getHarnessRequestOptions(authRequest).headers as any).Authorization, - ).toEqual(basicAuthentication); + (getHarnessRequestOptions(authRequest).headers as any)['x-api-key'], + ).toEqual('a'); }); }); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 0c4fb01970..324a57071c 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -34,7 +34,7 @@ export function getHarnessEditContentsUrl( url: string, ) { try { - const baseUrl = config.baseUrl ?? `https://${config.host}`; + const baseUrl = `https://${config.host}`; const [ _blank, _ng, @@ -61,9 +61,8 @@ export function getHarnessEditContentsUrl( } /** - * Given a URL pointing to a file, returns an api URL - * for fetching the contents of the data. - * + * Given a file path URL, + * it returns an API URL which returns the contents of the file. * @remarks * * Converts @@ -79,7 +78,7 @@ export function getHarnessFileContentsUrl( url: string, ) { try { - const baseUrl = config.baseUrl ?? `https://${config.host}`; + const baseUrl = `https://${config.host}`; const [ _blank, _ng, @@ -115,16 +114,14 @@ export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { headers?: Record; } { const headers: Record = {}; - const { username, token } = config; + const { token, apiKey } = config; if (!token) { return headers; } - if (username) { - headers.Authorization = `basic ${Buffer.from( - `${username}:${token}`, - ).toString('base64')}`; + if (apiKey) { + headers['x-api-key'] = apiKey; } else { headers.Authorization = `Bearer ${token}`; } From 6a0e918dc69d6e46d350485c4fea14c54cea3847 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 00:23:33 -0600 Subject: [PATCH 128/567] integration support for harness p3-comments Signed-off-by: Calvin Lee --- .../{HarnessCodeUrlReader.test.ts => HarnessUrlReader.test.ts} | 2 +- packages/integration/src/harness/core.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/backend-common/src/reading/{HarnessCodeUrlReader.test.ts => HarnessUrlReader.test.ts} (98%) diff --git a/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts similarity index 98% rename from packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts rename to packages/backend-common/src/reading/HarnessUrlReader.test.ts index 3e8b70a6aa..18c523b959 100644 --- a/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -60,7 +60,7 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { describe('HarnessUrlReader', () => { const worker = setupServer(); setupRequestMockHandlers(worker); - + beforeAll(() => worker.listen({ onUnhandledRequest: 'bypass' })); afterAll(() => { jest.clearAllMocks(); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 324a57071c..6de8039dd5 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -122,7 +122,7 @@ export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { if (apiKey) { headers['x-api-key'] = apiKey; - } else { + } else if (token) { headers.Authorization = `Bearer ${token}`; } From ed8b5324dad91129efa850a42f2d27b77b340806 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 16:41:40 -0600 Subject: [PATCH 129/567] integration support for harness p4-fixed test Signed-off-by: Calvin Lee --- .../src/reading/HarnessUrlReader.test.ts | 136 +++++++++--------- packages/integration/src/harness/core.ts | 8 +- 2 files changed, 71 insertions(+), 73 deletions(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index 18c523b959..4d43cebaec 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -25,7 +25,6 @@ import { UrlReaderPredicateTuple } from './types'; import { DefaultReadTreeResponseFactory } from './tree'; import getRawBody from 'raw-body'; import { HarnessUrlReader } from './HarnessUrlReader'; -import { NotFoundError } from '@backstage/errors'; const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), @@ -44,6 +43,7 @@ const harnessProcessor = new HarnessUrlReader( readHarnessConfig( new ConfigReader({ host: 'app.harness.io', + token: 'p', }), ), ), @@ -56,9 +56,60 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { treeResponseFactory, }); }; +const responseBuffer = Buffer.from('Apache License'); +const harnessApiResponse = (content: any) => { + return JSON.stringify({ + content: { + data: Buffer.from(content).toString('base64'), + encoding: 'base64', + }, + }); +}; + +const handlers = [ + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/all-apis.yaml', + (req, res, ctx) => { + return res(ctx.status(500), ctx.json({ message: 'Error!!!' })); + }, + ), + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/404error.yaml', + (req, res, ctx) => { + return res(ctx.status(404), ctx.json({ message: 'File not found.' })); + }, + ), + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/stream.TXT', + (req, res, ctx) => { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + }, + ), + + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/buffer.TXT', + (req, res, ctx) => { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + }, + ), + rest.post('/api/login', (req, res, ctx) => { + const { username } = req.body; + + if (username === 'admin') { + return res(ctx.status(200), ctx.json({ token: 'fake-token' })); + } + return res(ctx.status(403), ctx.json({ message: 'Access Denied' })); + }), +]; describe('HarnessUrlReader', () => { - const worker = setupServer(); + const worker = setupServer(...handlers); setupRequestMockHandlers(worker); beforeAll(() => worker.listen({ onUnhandledRequest: 'bypass' })); afterAll(() => { @@ -107,97 +158,40 @@ describe('HarnessUrlReader', () => { }); }); - describe('readUrl', () => { - const responseBuffer = Buffer.from('Apache License'); - const harnessApiResponse = (content: any) => { - return JSON.stringify({ - encoding: 'base64', - content: Buffer.from(content).toString('base64'), - }); - }; - - it.skip('should be able to read file contents as buffer', async () => { - worker.use( - rest.get( - 'https://app.harness.io/api/v1/repos/owner/project/contents/LICENSE', - (req, res, ctx) => { - // Test utils prefers matching URL directly but it is part of Gitea's API - if (req.url.searchParams.get('ref') === 'branch2') { - return res( - ctx.status(200), - ctx.body(harnessApiResponse(responseBuffer.toString())), - ); - } - - return res(ctx.status(500)); - }, - ), - ); - + describe('readUrl part 1', () => { + it('should be able to read file contents as buffer', async () => { const result = await harnessProcessor.readUrl( - 'https://app.harness.io/owner/project/src/branch/branch2/LICENSE', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/buffer.TXT', ); const buffer = await result.buffer(); expect(buffer.toString()).toBe(responseBuffer.toString()); }); - it.skip('should be able to read file contents as stream', async () => { - worker.use( - rest.get( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.txt', - (req, res, ctx) => { - if (req.url.searchParams.get('ref') === 'refMain') { - return res( - ctx.status(200), - ctx.body(harnessApiResponse(responseBuffer.toString())), - ); - } - - return res(ctx.status(500)); - }, - ), - ); - + it('should be able to read file contents as stream', async () => { const result = await harnessProcessor.readUrl( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.TXT', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/stream.TXT', ); const fromStream = await getRawBody(result.stream!()); expect(fromStream.toString()).toBe(responseBuffer.toString()); }); - it.skip('should raise NotFoundError on 404.', async () => { - worker.use( - rest.get( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', - (_, res, ctx) => { - return res(ctx.status(404, 'File not found.')); - }, - ), - ); - + it('should raise NotFoundError on 404.', async () => { await expect( harnessProcessor.readUrl( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml', ), - ).rejects.toThrow(NotFoundError); + ).rejects.toThrow( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/404error.yaml?routingId=accountId&include_commit=false&ref=refMain, 404 Not Found', + ); }); - it.skip('should throw an error on non 404 errors.', async () => { - worker.use( - rest.get( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', - (_, res, ctx) => { - return res(ctx.status(500, 'Error!!!')); - }, - ), - ); - + it('should throw an error on non 404 errors.', async () => { await expect( harnessProcessor.readUrl( 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', ), ).rejects.toThrow( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Error!!!', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Internal Server Error', ); }); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 6de8039dd5..f762cebb56 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -93,12 +93,16 @@ export function getHarnessFileContentsUrl( _repos, repoName, _files, - ref, + _ref, _branch, ...path ] = url.replace(baseUrl, '').split('/'); + const urlParts = url.replace(baseUrl, '').split('/'); + const refAndPath = urlParts.slice(13); + const refIndex = refAndPath.findIndex(item => item === '~'); + const refString = refAndPath.slice(0, refIndex); const pathWithoutSlash = path.join('/').replace(/^\//, ''); - return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${ref}`; + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${refString}`; } catch (e) { throw new Error(`Incorrect URL: ${url}, ${e}`); } From 0cf356671a830937d09872a933edcbc80e676841 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 16:43:47 -0600 Subject: [PATCH 130/567] integration support for harness p4-fixed test Signed-off-by: Calvin Lee --- .../backend-common/src/reading/HarnessUrlReader.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index 4d43cebaec..5e1d2d9b2e 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -98,14 +98,6 @@ const handlers = [ ); }, ), - rest.post('/api/login', (req, res, ctx) => { - const { username } = req.body; - - if (username === 'admin') { - return res(ctx.status(200), ctx.json({ token: 'fake-token' })); - } - return res(ctx.status(403), ctx.json({ message: 'Access Denied' })); - }), ]; describe('HarnessUrlReader', () => { From 1cfa4aa35ea08f16dc7635e87a608c6e8c41851c Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 21:13:04 -0600 Subject: [PATCH 131/567] integration support for harness p5-fixed lint Signed-off-by: Calvin Lee --- packages/backend-common/api-report.md | 3 +-- .../src/reading/HarnessUrlReader.test.ts | 8 ++++---- packages/integration/config.d.ts | 10 ++-------- packages/integration/src/harness/config.test.ts | 3 --- packages/integration/src/harness/config.ts | 3 --- 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 37bfda9861..33145fc47f 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -551,8 +551,7 @@ export class HarnessUrlReader implements UrlReader { // @public export type HarnessIntegrationConfig = { host: string; - baseUrl?: string; - username?: string; + apiKey?: string; token?: string; }; diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index 5e1d2d9b2e..d6cf9ab018 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -69,19 +69,19 @@ const harnessApiResponse = (content: any) => { const handlers = [ rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/all-apis.yaml', - (req, res, ctx) => { + (_req, res, ctx) => { return res(ctx.status(500), ctx.json({ message: 'Error!!!' })); }, ), rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/404error.yaml', - (req, res, ctx) => { + (_req, res, ctx) => { return res(ctx.status(404), ctx.json({ message: 'File not found.' })); }, ), rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/stream.TXT', - (req, res, ctx) => { + (_req, res, ctx) => { return res( ctx.status(200), ctx.body(harnessApiResponse(responseBuffer.toString())), @@ -91,7 +91,7 @@ const handlers = [ rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/buffer.TXT', - (req, res, ctx) => { + (_req, res, ctx) => { return res( ctx.status(200), ctx.body(harnessApiResponse(responseBuffer.toString())), diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 01f1f44d73..58dd9153c4 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -353,16 +353,10 @@ export interface Config { */ host: string; /** - * The base url for the Gitea instance. - * @visibility frontend - */ - baseUrl?: string; - - /** - * The username to use for authenticated requests. + * The apikey to use for authenticated requests. * @visibility secret */ - username?: string; + apiKey?: string; /** * Harness Code token used to authenticate requests. This can be either a generated access token. * @visibility secret diff --git a/packages/integration/src/harness/config.test.ts b/packages/integration/src/harness/config.test.ts index 4f6d8496e3..93be1fcc32 100644 --- a/packages/integration/src/harness/config.test.ts +++ b/packages/integration/src/harness/config.test.ts @@ -81,9 +81,6 @@ describe('readHarnessConfig', () => { expect(() => readHarnessConfig(buildConfig({ ...valid, host: 2 }))).toThrow( /host/, ); - expect(() => - readHarnessConfig(buildConfig({ ...valid, baseUrl: 2 })), - ).toThrow(/baseUrl/); }); it('works on the frontend', async () => { diff --git a/packages/integration/src/harness/config.ts b/packages/integration/src/harness/config.ts index 748cd141c5..2f75915567 100644 --- a/packages/integration/src/harness/config.ts +++ b/packages/integration/src/harness/config.ts @@ -44,7 +44,6 @@ export type HarnessIntegrationConfig = { */ export function readHarnessConfig(config: Config): HarnessIntegrationConfig { const host = config.getString('host'); - let baseUrl = config.getOptionalString('baseUrl'); const token = config.getOptionalString('token'); const apiKey = config.getOptionalString('apiKey'); @@ -54,8 +53,6 @@ export function readHarnessConfig(config: Config): HarnessIntegrationConfig { ); } - baseUrl = `https://${host}`; - return { host, apiKey, From 2bf97f0aa15b1940fb2a485584ae14c773cf7591 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 21:31:44 -0600 Subject: [PATCH 132/567] integration support for harness p5-fixed check Signed-off-by: Calvin Lee --- packages/integration/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/integration/package.json b/packages/integration/package.json index bbd0eecbe7..b57b94f982 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -50,6 +50,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", + "@backstage/test-utils": "workspace:^", "@types/luxon": "^3.0.0", "msw": "^1.0.0" }, diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..60b03d4399 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4368,6 +4368,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/test-utils": "workspace:^" "@octokit/auth-app": ^4.0.0 "@octokit/rest": ^19.0.3 "@types/luxon": ^3.0.0 From 049a69f223e9c986312750e55c4fab028398fc25 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 21:46:58 -0600 Subject: [PATCH 133/567] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee --- packages/integration/api-report.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index b40b66b0b6..2029dac600 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -520,9 +520,7 @@ export function getHarnessFileContentsUrl( ): string; // @public -export function getHarnessRequestOptions( - config: HarnessIntegrationConfig, -): { +export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { headers?: Record; }; @@ -711,8 +709,8 @@ export class HarnessIntegration implements ScmIntegration { // @public export type HarnessIntegrationConfig = { host: string; - apiKey?: string; token?: string; + apiKey?: string; }; // @public @@ -883,9 +881,7 @@ export function readGoogleGcsIntegrationConfig( ): GoogleGcsIntegrationConfig; // @public -export function readHarnessConfig( - config: Config, -): HarnessIntegrationConfig; +export function readHarnessConfig(config: Config): HarnessIntegrationConfig; // @public @deprecated (undocumented) export const replaceGitHubUrlType: typeof replaceGithubUrlType; From 7362e25a24086d47c28c98029cb4ff982af102f5 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 21:59:08 -0600 Subject: [PATCH 134/567] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee --- packages/backend-common/api-report.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 33145fc47f..1ae7a9dceb 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -531,6 +531,8 @@ export class GitlabUrlReader implements UrlReader { toString(): string; } +// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver +// // @public export class HarnessUrlReader implements UrlReader { constructor(integration: HarnessIntegration); @@ -548,13 +550,6 @@ export class HarnessUrlReader implements UrlReader { toString(): string; } -// @public -export type HarnessIntegrationConfig = { - host: string; - apiKey?: string; - token?: string; -}; - // @public export const HostDiscovery: typeof HostDiscovery_2; From a102a02c27a380513f04f8807c7eea2f5718998b Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 22:42:20 -0600 Subject: [PATCH 135/567] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee --- packages/backend-common/api-report.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 1ae7a9dceb..e815973fb1 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -531,8 +531,6 @@ export class GitlabUrlReader implements UrlReader { toString(): string; } -// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver -// // @public export class HarnessUrlReader implements UrlReader { constructor(integration: HarnessIntegration); From a25b566c6453d2ad5f04e3b4a31b896a3864c797 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 22:57:03 -0600 Subject: [PATCH 136/567] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee --- packages/backend-common/src/reading/HarnessUrlReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index 95114ae9d6..55854d102f 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -36,7 +36,7 @@ import { import { Readable } from 'stream'; /** - * Implements a {@link UrlReader} for the Harness code v1 api. + * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for the Harness code v1 api. * * @public */ From 645580361b68f525c227c9c2f5fcedc3cb89c3f2 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 23 Apr 2024 23:06:29 -0600 Subject: [PATCH 137/567] integration support for harness p5-fixed api microsite Signed-off-by: Calvin Lee --- microsite/sidebars.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 8291549153..eadcbbf806 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -239,9 +239,9 @@ "items": ["integrations/gitea/locations"] }, { - "type": "subcategory", + "type": "category", "label": "Harness", - "ids": ["integrations/harness/locations"] + "items": ["integrations/harness/locations"] }, { "type": "category", From d01f3ab8901274470ff160e3bca38d3b22372f96 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Thu, 25 Apr 2024 11:41:13 -0600 Subject: [PATCH 138/567] integration support for harness p5-fixed api microsite-fake Signed-off-by: Calvin Lee --- packages/backend-common/src/reading/HarnessUrlReader.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index 55854d102f..7d65e674b8 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -38,6 +38,7 @@ import { Readable } from 'stream'; /** * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for the Harness code v1 api. * + * * @public */ export class HarnessUrlReader implements UrlReader { From 9093f35e8aa1a99c1d7620a2ca3da6d19deeeefb Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Mon, 29 Apr 2024 12:41:19 -0600 Subject: [PATCH 139/567] integration support for harness p5-fixed api microsite-fix content api Signed-off-by: Calvin Lee --- .../src/reading/HarnessUrlReader.test.ts | 19 +++++++------------ .../src/reading/HarnessUrlReader.ts | 10 +++++----- packages/integration/src/harness/core.test.ts | 2 +- packages/integration/src/harness/core.ts | 2 +- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index d6cf9ab018..bb09baa140 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -58,29 +58,24 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { }; const responseBuffer = Buffer.from('Apache License'); const harnessApiResponse = (content: any) => { - return JSON.stringify({ - content: { - data: Buffer.from(content).toString('base64'), - encoding: 'base64', - }, - }); + return content; }; const handlers = [ rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/all-apis.yaml', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/all-apis.yaml', (_req, res, ctx) => { return res(ctx.status(500), ctx.json({ message: 'Error!!!' })); }, ), rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/404error.yaml', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/404error.yaml', (_req, res, ctx) => { return res(ctx.status(404), ctx.json({ message: 'File not found.' })); }, ), rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/stream.TXT', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/stream.TXT', (_req, res, ctx) => { return res( ctx.status(200), @@ -90,7 +85,7 @@ const handlers = [ ), rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/buffer.TXT', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/buffer.TXT', (_req, res, ctx) => { return res( ctx.status(200), @@ -173,7 +168,7 @@ describe('HarnessUrlReader', () => { 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml', ), ).rejects.toThrow( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/404error.yaml?routingId=accountId&include_commit=false&ref=refMain, 404 Not Found', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/404error.yaml?routingId=accountId&git_ref=refMain, 404 Not Found', ); }); @@ -183,7 +178,7 @@ describe('HarnessUrlReader', () => { 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', ), ).rejects.toThrow( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Internal Server Error', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/all-apis.yaml?routingId=accountId&git_ref=refMain, 500 Internal Server Error', ); }); }); diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index 7d65e674b8..09291c5176 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -79,18 +79,18 @@ export class HarnessUrlReader implements UrlReader { } if (response.ok) { - // Harness Code returns an object with the file contents encoded, not the file itself - const jsonResponse = await response.json(); - if (jsonResponse?.content?.encoding === 'base64') { + // Harness Code returns the raw content object + const jsonResponse = { data: response.body }; + if (jsonResponse) { return ReadUrlResponseFactory.fromReadable( - Readable.from(Buffer.from(jsonResponse?.content?.data, 'base64')), + Readable.from(jsonResponse.data), { etag: response.headers.get('ETag') ?? undefined, }, ); } - throw new Error(`Unknown encoding: ${jsonResponse?.content?.encoding}`); + throw new Error(`Unknown json: ${jsonResponse}`); } const message = `${url} x ${blobUrl}, ${response.status} ${response.statusText}`; diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts index 2a214cc06f..abfbb5d4c2 100644 --- a/packages/integration/src/harness/core.test.ts +++ b/packages/integration/src/harness/core.test.ts @@ -38,7 +38,7 @@ describe('Harness code core', () => { 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', ), ).toEqual( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/all-apis.yaml?routingId=accountId&git_ref=refMain', ); }); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index f762cebb56..8eba850ec2 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -102,7 +102,7 @@ export function getHarnessFileContentsUrl( const refIndex = refAndPath.findIndex(item => item === '~'); const refString = refAndPath.slice(0, refIndex); const pathWithoutSlash = path.join('/').replace(/^\//, ''); - return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${refString}`; + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/raw/${pathWithoutSlash}?routingId=${accountId}&git_ref=${refString}`; } catch (e) { throw new Error(`Incorrect URL: ${url}, ${e}`); } From 84bb2ed37df5312b0a0ed5b9195e2cd329487ec5 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 30 Apr 2024 02:57:29 -0600 Subject: [PATCH 140/567] integration support for harness - fixed comments Signed-off-by: Calvin Lee --- .changeset/empty-beers-relax.md | 2 +- .changeset/tasty-rats-explain.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/empty-beers-relax.md b/.changeset/empty-beers-relax.md index a285b9e7b4..bbfcef0933 100644 --- a/.changeset/empty-beers-relax.md +++ b/.changeset/empty-beers-relax.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -This patch adds HarnessURLReader. It only supports readUrl for now. readTree and search will be implemented next. +Added `HarnessURLReader` with `readUrl` support. diff --git a/.changeset/tasty-rats-explain.md b/.changeset/tasty-rats-explain.md index aee8057915..b97ee28fef 100644 --- a/.changeset/tasty-rats-explain.md +++ b/.changeset/tasty-rats-explain.md @@ -2,4 +2,4 @@ '@backstage/integration': minor --- -This patch brings Harness Code as a valid integration via the ScmIntgration interface. It adds harness code to the relevant static properties ( get integration by name, get integration by type) for plugs to be able to reference the same harness code server +Added `HarnessIntegration` via the `ScmIntegrations` interface. From 99e6105d1e97a2e38302b6144dda1a23c1506529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 30 Apr 2024 11:27:27 +0200 Subject: [PATCH 141/567] Fix ownership card sometimes locking up for complex org structures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/wet-files-pretend.md | 5 +++++ .../Cards/OwnershipCard/useGetEntities.ts | 22 +++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 .changeset/wet-files-pretend.md diff --git a/.changeset/wet-files-pretend.md b/.changeset/wet-files-pretend.md new file mode 100644 index 0000000000..8f77a5bc0b --- /dev/null +++ b/.changeset/wet-files-pretend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Fix ownership card sometimes locking up for complex org structures diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts index e7c1f132c3..5a708bcfee 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -34,7 +34,7 @@ import qs from 'qs'; import { EntityRelationAggregation } from '../types'; import { uniq } from 'lodash'; -const limiter = limiterFactory(10); +const limiter = limiterFactory(5); type EntityTypeProps = { kind: string; @@ -92,10 +92,12 @@ const getChildOwnershipEntityRefs = async ( const entityRef = stringifyEntityRef(entity); if (hasChildGroups) { const entityRefs = childGroups.map(r => stringifyEntityRef(r)); - const childGroupResponse = await catalogApi.getEntitiesByRefs({ - fields: ['kind', 'metadata.namespace', 'metadata.name', 'relations'], - entityRefs, - }); + const childGroupResponse = await limiter(() => + catalogApi.getEntitiesByRefs({ + fields: ['kind', 'metadata.namespace', 'metadata.name', 'relations'], + entityRefs, + }), + ); const childGroupEntities = childGroupResponse.items.filter(isEntity); const unknownChildren = childGroupEntities.filter( @@ -107,12 +109,10 @@ const getChildOwnershipEntityRefs = async ( const childrenRefs = ( await Promise.all( unknownChildren.map(childGroupEntity => - limiter(() => - getChildOwnershipEntityRefs(childGroupEntity, catalogApi, [ - ...alreadyRetrievedParentRefs, - entityRef, - ]), - ), + getChildOwnershipEntityRefs(childGroupEntity, catalogApi, [ + ...alreadyRetrievedParentRefs, + entityRef, + ]), ), ) ).flatMap(aggregated => aggregated); From 015149cffde95f1cdf282841639422975862a510 Mon Sep 17 00:00:00 2001 From: Marley <55280588+marleypowell@users.noreply.github.com> Date: Fri, 19 Apr 2024 09:09:40 +0000 Subject: [PATCH 142/567] fix: added `eventsServiceFactory` to `defaultServiceFactories` Signed-off-by: Marley <55280588+marleypowell@users.noreply.github.com> --- packages/backend-defaults/package.json | 29 ++++++++++--------- .../backend-defaults/src/CreateBackend.ts | 2 ++ packages/backend-test-utils/package.json | 1 + .../src/next/services/mockServices.ts | 12 ++++++++ .../src/next/wiring/TestBackend.ts | 1 + plugins/events-node/src/index.ts | 2 +- plugins/events-node/src/service.ts | 29 ++++++++++--------- 7 files changed, 47 insertions(+), 29 deletions(-) diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 8b061b15f4..d2c86e46c8 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,5 +1,6 @@ { "name": "@backstage/backend-defaults", + "version": "0.2.17", "description": "Backend defaults used by Backstage backend apps", "version": "0.2.18-next.0", "main": "src/index.ts", @@ -9,38 +10,38 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, - "backstage": { - "role": "node-library" - }, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/backend-defaults" }, - "keywords": [ - "backstage" - ], "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-app-api": "workspace:^", - "@backstage/backend-common": "workspace:^" + "@backstage/backend-common": "workspace:^", + "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^" - }, - "files": [ - "dist" - ] + } } diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 5495665dd8..e9bdc03262 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -36,6 +36,7 @@ import { httpAuthServiceFactory, userInfoServiceFactory, } from '@backstage/backend-app-api'; +import { eventsServiceFactory } from '@backstage/plugin-events-node'; export const defaultServiceFactories = [ authServiceFactory(), @@ -56,6 +57,7 @@ export const defaultServiceFactories = [ tokenManagerServiceFactory(), userInfoServiceFactory(), urlReaderServiceFactory(), + eventsServiceFactory(), ]; /** diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 1d43e24adf..e252d267da 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -51,6 +51,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", "@backstage/types": "workspace:^", "better-sqlite3": "^9.0.0", "cookie": "^0.6.0", diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index a858bf7dbf..05b3ae927c 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -52,6 +52,10 @@ import { MockAuthService } from './MockAuthService'; import { MockHttpAuthService } from './MockHttpAuthService'; import { mockCredentials } from './mockCredentials'; import { MockUserInfoService } from './MockUserInfoService'; +import { + eventsServiceFactory, + eventsServiceRef, +} from '@backstage/plugin-events-node'; /** @internal */ function simpleFactory< @@ -397,4 +401,12 @@ export namespace mockServices { search: jest.fn(), })); } + + export namespace events { + export const factory = eventsServiceFactory; + export const mock = simpleMock(eventsServiceRef, () => ({ + publish: jest.fn(), + subscribe: jest.fn(), + })); + } } diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 10041dce4f..e43353991f 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -83,6 +83,7 @@ export const defaultServiceFactories = [ mockServices.tokenManager.factory(), mockServices.userInfo.factory(), mockServices.urlReader.factory(), + mockServices.events.factory(), ]; /** diff --git a/plugins/events-node/src/index.ts b/plugins/events-node/src/index.ts index 2bd93a8aea..643ba7f79e 100644 --- a/plugins/events-node/src/index.ts +++ b/plugins/events-node/src/index.ts @@ -22,4 +22,4 @@ export * from './api'; export * from './deprecated'; -export { eventsServiceRef } from './service'; +export { eventsServiceRef, eventsServiceFactory } from './service'; diff --git a/plugins/events-node/src/service.ts b/plugins/events-node/src/service.ts index e1d3047fca..af0e562c80 100644 --- a/plugins/events-node/src/service.ts +++ b/plugins/events-node/src/service.ts @@ -30,18 +30,19 @@ import { EventsService, DefaultEventsService } from './api'; export const eventsServiceRef = createServiceRef({ id: 'events.service', scope: 'plugin', - defaultFactory: async service => - createServiceFactory({ - service, - deps: { - pluginMetadata: coreServices.pluginMetadata, - rootLogger: coreServices.rootLogger, - }, - async createRootContext({ rootLogger }) { - return DefaultEventsService.create({ logger: rootLogger }); - }, - async factory({ pluginMetadata }, eventsService) { - return eventsService.forPlugin(pluginMetadata.getId()); - }, - }), +}); + +/** @public */ +export const eventsServiceFactory = createServiceFactory({ + service: eventsServiceRef, + deps: { + pluginMetadata: coreServices.pluginMetadata, + rootLogger: coreServices.rootLogger, + }, + async createRootContext({ rootLogger }) { + return DefaultEventsService.create({ logger: rootLogger }); + }, + async factory({ pluginMetadata }, eventsService) { + return eventsService.forPlugin(pluginMetadata.getId()); + }, }); From 7e5a50d446cd9474f3befe3e2ea88ea8e4e14800 Mon Sep 17 00:00:00 2001 From: Marley <55280588+marleypowell@users.noreply.github.com> Date: Fri, 19 Apr 2024 09:12:20 +0000 Subject: [PATCH 143/567] added changeset Signed-off-by: Marley <55280588+marleypowell@users.noreply.github.com> --- .changeset/quick-cats-argue.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/quick-cats-argue.md diff --git a/.changeset/quick-cats-argue.md b/.changeset/quick-cats-argue.md new file mode 100644 index 0000000000..956f038f34 --- /dev/null +++ b/.changeset/quick-cats-argue.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-test-utils': patch +'@backstage/backend-defaults': patch +'@backstage/plugin-events-node': patch +--- + +added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used From f8b04f61f516b35ec74b44b6d38c4527812b0dc2 Mon Sep 17 00:00:00 2001 From: Marley <55280588+marleypowell@users.noreply.github.com> Date: Fri, 19 Apr 2024 09:15:19 +0000 Subject: [PATCH 144/567] updated yarn.lock Signed-off-by: Marley <55280588+marleypowell@users.noreply.github.com> --- yarn.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..e4c522b43f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3432,6 +3432,7 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" languageName: unknown linkType: soft @@ -3550,6 +3551,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" "@backstage/types": "workspace:^" "@types/supertest": ^2.0.8 better-sqlite3: ^9.0.0 From 58cc659d9997fa056cf92b64ff6dcbd7483395f6 Mon Sep 17 00:00:00 2001 From: Marley <55280588+marleypowell@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:31:03 +0000 Subject: [PATCH 145/567] Merge remote-tracking branch 'upstream/master' into marley/provide-default-events-factory Signed-off-by: Marley <55280588+marleypowell@users.noreply.github.com> From 26a2c2bcfbd7f978844a2e9d43cddf71aad27933 Mon Sep 17 00:00:00 2001 From: Marley <55280588+marleypowell@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:35:13 +0000 Subject: [PATCH 146/567] rebase fix Signed-off-by: Marley <55280588+marleypowell@users.noreply.github.com> --- packages/backend-defaults/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index d2c86e46c8..acfabf2779 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,10 +1,10 @@ { "name": "@backstage/backend-defaults", - "version": "0.2.17", - "description": "Backend defaults used by Backstage backend apps", "version": "0.2.18-next.0", - "main": "src/index.ts", - "types": "src/index.ts", + "description": "Backend defaults used by Backstage backend apps", + "backstage": { + "role": "node-library" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", From e6d20965104df029822a176fc7586f87fac5733a Mon Sep 17 00:00:00 2001 From: Marley <55280588+marleypowell@users.noreply.github.com> Date: Tue, 30 Apr 2024 09:40:57 +0000 Subject: [PATCH 147/567] rebase fix Signed-off-by: Marley <55280588+marleypowell@users.noreply.github.com> --- packages/backend-defaults/package.json | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index acfabf2779..d9ac8dcdb3 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,38 +1,35 @@ { "name": "@backstage/backend-defaults", - "version": "0.2.18-next.0", "description": "Backend defaults used by Backstage backend apps", - "backstage": { - "role": "node-library" - }, + "version": "0.2.18-next.0", + "main": "src/index.ts", + "types": "src/index.ts", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, - "keywords": [ - "backstage" - ], + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/backend-defaults" }, - "license": "Apache-2.0", - "main": "src/index.ts", - "types": "src/index.ts", - "files": [ - "dist" + "keywords": [ + "backstage" ], + "license": "Apache-2.0", "scripts": { "build": "backstage-cli package build", - "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "start": "backstage-cli package start", - "test": "backstage-cli package test" + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { "@backstage/backend-app-api": "workspace:^", @@ -43,5 +40,8 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^" - } + }, + "files": [ + "dist" + ] } From 0a63f600a999b6aab2a3425a325c22e2578d6e3f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 30 Apr 2024 11:52:23 +0200 Subject: [PATCH 148/567] remove GOVERNANCE.md Signed-off-by: Patrik Oldsberg --- GOVERNANCE.md | 233 ------------------ OWNERS.md | 2 +- docs/faq/technical.md | 2 +- ...021-06-22-spotify-backstage-is-growing.mdx | 4 +- microsite/blog/2023-04-26-kubecon-eu-2023.mdx | 2 +- .../blog/2024-04-19-community-plugins.mdx | 2 +- 6 files changed, 6 insertions(+), 239 deletions(-) delete mode 100644 GOVERNANCE.md diff --git a/GOVERNANCE.md b/GOVERNANCE.md deleted file mode 100644 index 2367dab99c..0000000000 --- a/GOVERNANCE.md +++ /dev/null @@ -1,233 +0,0 @@ -# Project Areas - -The Backstage project is divided into several project areas, each covering particular parts of the project. The main driver for each area is ownership of code in the main Backstage repository, as well as other repositories in the Backstage GitHub organization. Each area has a set of maintainers and repository content that they own. There may be no overlap in ownership between areas, which means that any given line in the GitHub code owners file should have only one owner specified. Each area is represented by a team in the Backstage GitHub organization. Apart from certain project-wide concerns, such as the release process, each area is self-governing and chooses their own ways of working. Project areas may also have special interest groups (SIGs) related to their area, but this is not required. - -The project areas as well as their maintainers are listed in the [OWNERS.md](./OWNERS.md) file. - -Each project area must have at least one maintainer. Project area maintainers may have shared ownership with the core maintainers, which in that case is considered an incubating area. The project area maintainers help drive work forward in the area, but they might not yet feel ready to take on ownership. The goal should generally be that these project area maintainers eventually become sole maintainers of the project area. This is to allow for a more smooth onboarding and transition of ownership, where members of the community might for example be new to open source maintainership. - -## Adding new project areas - -Project areas are added by nominating new maintainers for that area. See the sections for becoming a [Project Area Maintainer](#project-area-maintainer). - -Project areas may also by added by splitting existing areas. Every area that is created through this process must have at least one maintainer. - -## Removing project areas - -Project areas are removed by removing all maintainers for that area and removing the corresponding team from the Backstage GitHub organization. The project area can be re-added later if there is a need for it. Reasons for removal may include lack of activity, lack of maintainers, or lack of relevance to the project. - -# Project Roles - -## Contributor - -A Contributor contributes directly to the project and adds value to it. Contributions need not be code. People at the Contributor level may be new contributors, or they may only contribute occasionally. - -### Responsibilities - -- Follow the [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) -- Follow the project [contributing guide](CONTRIBUTING.md) - -### How to get involved - -- Participate in community discussions -- Help other users -- Submit bug reports -- Comment on issues -- Try out new releases -- Attend community events - -### How to contribute - -- Report and sometimes resolve issues -- Occasionally submit PRs -- Contribute to the documentation -- Show up at meetings, take notes -- Answer questions from other community members -- Submit feedback on issues and PRs -- Test releases and patches and submit reviews -- Run or help run events -- Promote the project in public - -## Organization Member - -An org member is a frequent contributor that has become a member of the Backstage GitHub organization. In addition to the responsibilities of contributors, an org member is also expected to be reasonably active in the community through continuous contributions of any type. - -An Organization Member must meet the responsibilities and has the requirements of a Contributor. - -### Responsibilities - -- Continues to contribute regularly, as demonstrated by having at least 10 GitHub contributions per year in [Devstats](https://backstage.devstats.cncf.io/d/48/users-statistics-by-repository-group?orgId=1&var-period=y&var-metric=contributions&var-repogroup_name=All&from=now-1y&to=now&var-users=All), or contributions of a similar effort that might not be captured in Devstats. - -### Requirements - -- Must have at least 10 contributions to the projects in the form of: - - Accepted PRs - - Helpful PR reviews - - Resolving GitHub issues - - Or some equivalent contributions to the project -- Must have been contributing for at least 3 months -- Or is the member of a team that owns a project area, in which case the above requirements do not apply and the member is instead vetted by the project area maintainers - -### Becoming an Organization Member - -Open an issue towards [the community repository](https://github.com/backstage/community) using the [org membership request template](https://github.com/backstage/community/issues/new?template=org_member.yaml&title=Org+Member%3A+%3Cyour-github-login%3E). - -### Privileges - -- Membership in the Backstage GitHub organization - -## Plugin Maintainer - -A Plugin Maintainer is responsible for maintaining an individual Backstage plugin or module. This includes reviewing contributions and responding to issues towards an individual plugin, as well as keeping the plugin up to date. - -Plugin Maintainer is a lightweight form of ownership that is primarily reflected though code owners of the plugin packages in the [CODEOWNERS](./.github/CODEOWNERS) file. Each plugin can have one or more maintainers. If a plugin becomes a significant part of the Backstage ecosystem, it may be promoted to be a distinct project area instead. - -A Plugin Maintainer has all the rights and responsibilities of an Organization Member. - -### Responsibilities - -- Review the majority of PRs towards the plugin -- Respond to GitHub issues related to the plugin -- Keep the plugin up-to-date with Backstage libraries and other dependencies -- Follow the [reviewing guide](REVIEWING.md) - -### Requirements - -- Is an Organization Member -- Display knowledge of Backstage's review process and best practices for plugin design -- Is supportive of new and occasional contributors and helps get useful PRs in shape to merge - -### Privileges - -- GitHub code owner of the plugin directory, with rights to approve and merge PRs towards the plugin - -### Becoming a Plugin Maintainer - -To become a Plugin Maintainer, you first need to be an Organization Member. You can then file a pull request towards [CODEOWNERS.md](./.github/CODEOWNERS) requesting to be added as a code owner of the plugin directory. Existing code owners of that plugin alongside the core maintainers will then review the request. - -## Project Area Maintainer - -Project Area Maintainers are owners of a particular project area. They are expected to review and merge pull requests towards their area, and also drive development and manage tech health. A Project Area Maintainer also need to commit a certain number of hours per month towards the project, and exercise judgment for the good of the project, independent of their employer. Project Area Maintainers should also mentor new maintainers and participate in and lead community meetings related to their area. New Project Area Maintainers need to be approved by the existing project area maintainers, or the core maintainers if it is a new area. - -The maintainers of a project area may be represented by a team in external organization. In this case, the state as a project area maintainer is tied to the membership in that team. New members of the team may automatically be added as maintainers, as well as removed when they leave. This process is governed autonomously by the team of project area maintainers. - -A Project Area Maintainer has all the rights and responsibilities of an Organization Member. - -### Responsibilities - -- Review PRs towards their project area. Project area maintainers are expected to review at least 20 PRs per year, or the majority of all PRs towards the area, if it is less than 20 -- Follow the [reviewing guide](REVIEWING.md) -- Triage and respond to issues related to their project area -- Mentor new project area maintainers -- Write refactoring PRs -- Determine strategy and policy for the project area -- Participate in or leading community meetings related to their project area - -### Requirements - -- Is an Organization Member -- Have made at least 5 meaningful contributions towards the project area -- Demonstrates knowledge of their project area, and how it fits into the larger Backstage project -- Is able to exercise judgment for the good of the project, independent of their employer, friends, or team -- Mentors other contributors and project area maintainers -- Can commit to spending at least 16 hours per month working on the project, preferably distributed evenly across the month - -### Privileges - -- Approve and merge PRs towards their project area -- Drive the direction and roadmap of their project area - -### Becoming a Project Area Maintainer - -If you are interested in becoming a project area maintainer, reach out to the existing maintainers for that area. If you wish to become a maintainer for a new area, reach out to the core maintainers. - -Any current project area maintainer or core maintainer may nominate a new project area maintainer by opening a PR towards the [OWNERS.md](OWNERS.md) file. A majority of the project area maintainers for that area must approve the PR. If there are no existing maintainers for that area, the PR must be approved by a majority of the core maintainers. - -## Core Maintainer - -Core Maintainers are responsible for the Backstage project as a whole. They help review and merge project-level pull requests as well as coordinate work affecting multiple project areas. A core maintainer needs to commit the majority of their working time towards the project, and exercise judgment for the good of the project, independent of their employer. Core maintainers should also mentor and seek out new maintainers, lead community meetings, and communicate with the CNCF on behalf of the project. To become a core maintainer one needs to have been the maintainer of a number of different project areas, demonstrate a deep knowledge of large parts of the Backstage project, and be backed by the existing core maintainers. - -A Core Maintainer have all the rights and responsibilities of a Project Area Maintainer. - -### Responsibilities - -- Take part in the incoming issue and PR triage and review process. PRs are shared equally among all maintainers -- Mentor new Project Area Maintainers and Plugin Maintainers -- Drive refactoring and manage tech health across the entire project -- Participate in CNCF maintainer activities -- Respond to security incidents in accordance to our [security policy](./SECURITY.md) -- Determine strategy and policy for the project -- Participate in or leading community meetings - -### Requirements - -- Experience as a Project Area Maintainer for at least 6 months -- Demonstrates a broad knowledge of the project across multiple areas -- Is able to exercise judgment for the good of the project, independent of their employer, friends, or team -- Mentors other contributors -- Can commit to spending at least 10 days per month working on the project - -### Privileges - -- Approve PRs that fall outside any specific project area -- Merge PRs to any area of the project -- Represent the project in public as a Maintainer -- Communicate with the CNCF on behalf of the project -- Have a vote in Maintainer decision-making meetings - -### Becoming a Core Maintainer - -Any core maintainer or end user sponsor may nominate a new core maintainer by opening a PR towards the [OWNERS.md](OWNERS.md) file. Core maintainers must be approved by a majority of the existing core maintainers and end user sponsors. - -## End User Sponsors - -### Role of a Backstage End User Sponsor - -- Provide support for Backstage by removing blockers, securing funding, providing advocacy, feedback, and ensuring project continuity and long term success -- Assist Backstage maintainers in prioritizing upcoming roadmap items and planned work -- Provide neutral mediation for any disputes that arise as part of the project - -### Backstage End User Sponsor Membership - -The End User Sponsors group comprises at most 5 people. To be eligible for membership in the group, you or the company where you work you must: - -- Be responsible for and end user of a production Backstage deployment of non-trivial size -- Be active contributors to the open source project -- Be willing and able to attend regularly-scheduled End User Sponsor meetings -- Abide by [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) - -Candidates for membership will be nominated by current Sponsor members or by Backstage maintainers. If there are more nominations than Sponsor seats remaining, existing sponsors shall vote on the candidates, and the candidates with the most votes will become Sponsors. Any ties will be broken by current Backstage sponsors. - -# Conflict resolution and voting - -In general, we prefer that technical issues and membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and core maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting. - -In all cases in this document where voting is mentioned, the voting process is a simple majority in which each sponsor receives two votes and each core maintainer receives one vote. If such a majority is reached, the vote is said to have _passed_. - -## Inactivity - -It is important for contributors to be and stay active to set an example and show commitment to the project. Inactivity is harmful to the project as it may lead to unexpected delays, contributor attrition, and a loss of trust in the project. - -Inactivity is measured by periods of no contributions without explanation, for longer than: - -- Core Maintainer: 2 months -- Project Area Maintainer: 4 months -- Plugin Maintainer: 6 months -- Organization Member: 12 months - -Consequences of being inactive include: - -- Involuntary removal or demotion -- Being asked to move to Emeritus status - -## Involuntary Removal or Demotion - -Involuntary removal/demotion of a contributor happens when responsibilities and requirements aren't being met. This may include repeated patterns of inactivity, extended period of inactivity, a period of failing to meet the requirements of your role, and/or a violation of the Code of Conduct. This process is important because it protects the community and its deliverables while also opens up opportunities for new contributors to step in. - -Involuntary removal or demotion is handled through a vote by a majority of the current Core Maintainers. Some aspects of this process may be automated, such as removal after periods of inactivity. - -## Stepping Down/Emeritus Process - -If and when contributors' commitment levels change, contributors can consider stepping down (moving down the contributor ladder) vs moving to emeritus status (completely stepping away from the project). - -Contact the Maintainers about changing to Emeritus status, or reducing your contributor level. diff --git a/OWNERS.md b/OWNERS.md index ab35b5b1e5..d8967ca873 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -1,5 +1,5 @@ - See [CONTRIBUTING.md](CONTRIBUTING.md) for general contribution guidelines. -- See [GOVERNANCE.md](GOVERNANCE.md) for governance guidelines and responsibilities. +- See [GOVERNANCE.md](https://github.com/backstage/community/blob/main/GOVERNANCE.md) for governance guidelines and responsibilities. ## Core Maintainers diff --git a/docs/faq/technical.md b/docs/faq/technical.md index ffd99dadcd..d64d81d499 100644 --- a/docs/faq/technical.md +++ b/docs/faq/technical.md @@ -154,7 +154,7 @@ maintains Backstage in your own environment. For more information, see our [Owners](https://github.com/backstage/backstage/blob/master/OWNERS.md) and -[Governance](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md). +[Governance](https://github.com/backstage/community/blob/main/GOVERNANCE.md). ### Does Spotify provide a managed version of Backstage? diff --git a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx index 6cae5c907e..308effaeab 100644 --- a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx +++ b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx @@ -66,9 +66,9 @@ Speaking of reviewers and maintainers… ## Adding reviewers and maintainers -[![GitHub logo](assets/21-06-22/gh-reviewers.png)](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers) +[![GitHub logo](assets/21-06-22/gh-reviewers.png)](https://github.com/backstage/community/blob/main/GOVERNANCE.md#reviewers) -We have introduced [reviewers](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers) to the project! By adding this new role, we’ve expanded the number of people who are permitted to approve and merge pull requests. This will offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors. +We have introduced [reviewers](https://github.com/backstage/community/blob/main/GOVERNANCE.md#reviewers) to the project! By adding this new role, we’ve expanded the number of people who are permitted to approve and merge pull requests. This will offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors. Of course, with these new efforts, we expect even more companies to adopt Backstage, which means the platform will continue to grow, and the number of PRs will continue to grow with it. As that happens, we hope to add to both the maintainer and reviewer teams in the future. diff --git a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx index 7ee549c088..8f369c2d14 100644 --- a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx +++ b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx @@ -21,7 +21,7 @@ On Tuesday the Backstage maintainers hosted a jam-packed project meeting. The co ![Patrik and Ben onstage for the State of Backstage talk](assets/2023-04-26/IMG_0120.png) -Core maintainers [Ben Lambert](https://github.com/benjdlambert) and [Patrik Oldsberg](https://github.com/Rugvip) took center stage on Wednesday for the Backstage Maintainer Track: State of Backstage in 2023 talk. Backstage has officially hit over 1,000 adopters and 1,000 contributors – so it’s apt timing to modernize the governance model for the project. Taking pointers from the [CNCF Contributor Ladder Governance Template](https://contribute.cncf.io/maintainers/templates/), a new [Backstage Governance Model](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md) is now in effect! Patrik walked us through the new ladder model which introduces a number of changes, one being the addition of [project area maintainers](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area-maintainer). This role lets members of the community take increased ownership over a specific area of interest, like Catalog, Discoverability, TechDocs, Helm Charts, and Kubernetes. New project areas that will be added include Permissions and Software Templates. New project areas can be proposed by nominating a project area maintainer for the area. The new model also adds an organization member role for contributors who want to take a more active role in the Backstage community. You can open an issue to become an organization member [here](https://github.com/backstage/community/issues/new/choose). +Core maintainers [Ben Lambert](https://github.com/benjdlambert) and [Patrik Oldsberg](https://github.com/Rugvip) took center stage on Wednesday for the Backstage Maintainer Track: State of Backstage in 2023 talk. Backstage has officially hit over 1,000 adopters and 1,000 contributors – so it’s apt timing to modernize the governance model for the project. Taking pointers from the [CNCF Contributor Ladder Governance Template](https://contribute.cncf.io/maintainers/templates/), a new [Backstage Governance Model](https://github.com/backstage/community/blob/main/GOVERNANCE.md) is now in effect! Patrik walked us through the new ladder model which introduces a number of changes, one being the addition of [project area maintainers](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area-maintainer). This role lets members of the community take increased ownership over a specific area of interest, like Catalog, Discoverability, TechDocs, Helm Charts, and Kubernetes. New project areas that will be added include Permissions and Software Templates. New project areas can be proposed by nominating a project area maintainer for the area. The new model also adds an organization member role for contributors who want to take a more active role in the Backstage community. You can open an issue to become an organization member [here](https://github.com/backstage/community/issues/new/choose). ![Contributor ladder](assets/2023-04-26/contributor_ladder.png) diff --git a/microsite/blog/2024-04-19-community-plugins.mdx b/microsite/blog/2024-04-19-community-plugins.mdx index bb8b9c2313..49d1765bce 100644 --- a/microsite/blog/2024-04-19-community-plugins.mdx +++ b/microsite/blog/2024-04-19-community-plugins.mdx @@ -14,7 +14,7 @@ For those who depended on these plugins, migrating is as simple as `yarn backsta ## The community plugins repo -Some of you who have been around a while, or have seen our [Maintainer Track talks](https://www.youtube.com/watch?v=ONMBYnhxnNU) at KubeCon, might have seen [this RFC](https://github.com/backstage/backstage/issues/20266) which outlines some issues with the scale of the `backstage/backstage` monorepo, and us as maintainers being the de facto owners of all plugins without a [project area](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area) or [plugin maintainer](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area-maintainer). +Some of you who have been around a while, or have seen our [Maintainer Track talks](https://www.youtube.com/watch?v=ONMBYnhxnNU) at KubeCon, might have seen [this RFC](https://github.com/backstage/backstage/issues/20266) which outlines some issues with the scale of the `backstage/backstage` monorepo, and us as maintainers being the de facto owners of all plugins without a [project area](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area) or [plugin maintainer](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area-maintainer). There was some great discussion in this issue, and some great ideas. One of the ideas was to create a dedicated home for community plugins, with all the burden of release tooling and workspace tooling already set up, which is a pretty big barrier for people wanting to create plugins for Backstage in their own organization or personal account. These plugins would then have the ability to release independently of the main monorepo, and have their own release cadence, which is something that we've been looking at exploring for a while. From 59fce6234cb832276234e9b6582eeab649ad1469 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 30 Apr 2024 12:51:15 +0200 Subject: [PATCH 149/567] Request to modify a notification payload. Signed-off-by: bnechyporenko --- beps/0001-notifications-system/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/beps/0001-notifications-system/README.md b/beps/0001-notifications-system/README.md index 4b4f3b0532..fb3e36f624 100644 --- a/beps/0001-notifications-system/README.md +++ b/beps/0001-notifications-system/README.md @@ -110,6 +110,7 @@ The notification backend stores notification using the [database service](https: - Topic (optional) - Scope (optional) - Icon (optional) + - Extra (optional) The recipients is **not** a list of users, but rather a filter that describes who should receive the notification. It must be possible to evaluate this filter in a database query, so that we can efficiently fetch all notifications for a given user. The same filter will also be used by the signal backend to determine which users should receive a signal. @@ -147,6 +148,8 @@ The link is a relative or absolute URL. As an example, it can be used: - by an external system to request an action within an asynchronous task - by a BE plugin to provide link to other part of the Backstage UI (i.e. to the Catalog) +The extra is a flexible JSON like field, where an additional payload can be stored. + The additional links are an array of title-URL pairs. They can represent immediate actions on the notification (i.e. yes-no) or lead the user to additional details. The `notification-backend` does not provide any new permissions, since creating notifications can only be done by other backend plugins, while reading notifications can only be done by the authenticated user. It is possible that we want to add a permissions for reading notifications, in particular for admin and impersonation use cases, but that is not part of this proposal or the initial implementation. From d4a3b10f02bae9888166cd386d1d875c364cdee2 Mon Sep 17 00:00:00 2001 From: secustor Date: Tue, 30 Apr 2024 14:00:43 +0200 Subject: [PATCH 150/567] feat: expose DD RUM sessionSampleRate and sessionReplaySampleRate Signed-off-by: secustor --- app-config.yaml | 3 + docs/integrations/datadog-rum/installation.md | 2 + packages/app-next/public/index.html | 5 +- packages/app/public/index.html | 5 +- packages/cli/package.json | 62 +++++++++++-------- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 9e5b5c468c..cb70170541 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -9,6 +9,9 @@ app: # applicationId: qwerty # site: # datadoghq.eu default = datadoghq.com # env: # optional + # sessionSampleRate: 100 + # sessionReplaySampleRate: 0 + support: url: https://github.com/backstage/backstage/issues # Used by common ErrorPage items: # Used by common SupportButton component diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 32eb2b3de8..4dc6c24134 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -22,6 +22,8 @@ app: applicationId: qwerty # site: datadoghq.eu # env: 'staging' + # sessionSampleRate: 100 + # sessionReplaySampleRate: 0 ``` If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. diff --git a/packages/app-next/public/index.html b/packages/app-next/public/index.html index 1bbfda590d..63ab0bec0c 100644 --- a/packages/app-next/public/index.html +++ b/packages/app-next/public/index.html @@ -72,7 +72,10 @@ site: '<%= config.getOptionalString("app.datadogRum.site") || "datadoghq.com" %>', service: 'backstage', env: '<%= config.getString("app.datadogRum.env") %>', - sampleRate: 100, + sampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionSampleRate") || 100 %>', + sessionReplaySampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionReplaySampleRate") || 0 %>', trackInteractions: true, }); }); diff --git a/packages/app/public/index.html b/packages/app/public/index.html index 631666da28..9a8267e98e 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -72,7 +72,10 @@ site: '<%= config.getOptionalString("app.datadogRum.site") || "datadoghq.com" %>', service: 'backstage', env: '<%= config.getString("app.datadogRum.env") %>', - sampleRate: 100, + sampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionSampleRate") || 100 %>', + sessionReplaySampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionReplaySampleRate") || 0 %>', trackInteractions: true, }); }); diff --git a/packages/cli/package.json b/packages/cli/package.json index ecdfa590d6..c8baae4d4d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,34 +1,46 @@ { "name": "@backstage/cli", - "description": "CLI for developing Backstage plugins and apps", "version": "0.26.5-next.0", - "publishConfig": { - "access": "public" - }, + "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/cli" }, - "keywords": [ - "backstage" - ], "license": "Apache-2.0", "main": "dist/index.cjs.js", - "scripts": { - "build": "backstage-cli package build", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test", - "clean": "backstage-cli package clean", - "start": "nodemon --" - }, "bin": { "backstage-cli": "bin/backstage-cli" }, + "files": [ + "asset-types", + "templates", + "config", + "bin", + "dist/**/*.js" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "start": "nodemon --", + "test": "backstage-cli package test" + }, + "nodemonConfig": { + "exec": "bin/backstage-cli", + "ext": "ts", + "watch": "./src" + }, "dependencies": { "@backstage/catalog-model": "workspace:^", "@backstage/cli-common": "workspace:^", @@ -197,18 +209,6 @@ "optional": true } }, - "files": [ - "asset-types", - "templates", - "config", - "bin", - "dist/**/*.js" - ], - "nodemonConfig": { - "watch": "./src", - "exec": "bin/backstage-cli", - "ext": "ts" - }, "configSchema": { "$schema": "https://backstage.io/schema/config-v1", "title": "@backstage/cli", @@ -248,6 +248,16 @@ "type": "string", "visibility": "frontend", "description": "site for Datadog RUM events" + }, + "sessionSampleRate": { + "type": "number", + "visibility": "frontend", + "description": "sample rate of Datadog RUM events" + }, + "sessionReplaySampleRate": { + "type": "number", + "visibility": "frontend", + "description": "sample rate of session replays based upon already sampled Datadog RUM events" } }, "required": [ From e4b50ab39bce59b285bc93838e3946b2d18503c3 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 30 Apr 2024 21:10:30 +0200 Subject: [PATCH 151/567] Scaffolder workspace serialization Signed-off-by: bnechyporenko --- .changeset/healthy-dots-ring.md | 6 + plugins/scaffolder-backend/api-report.md | 22 ++++ .../migrations/20240401213200_workspace.js | 35 ++++++ plugins/scaffolder-backend/package.json | 2 + .../src/scaffolder/tasks/DatabaseTaskStore.ts | 26 ++++ .../tasks/NunjucksWorkflowRunner.ts | 13 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 16 +++ .../src/scaffolder/tasks/serializer.test.ts | 111 ++++++++++++++++++ .../src/scaffolder/tasks/serializer.ts | 39 ++++++ .../src/scaffolder/tasks/types.ts | 13 ++ plugins/scaffolder-node/api-report.md | 4 + plugins/scaffolder-node/src/tasks/types.ts | 7 ++ yarn.lock | 2 + 13 files changed, 292 insertions(+), 4 deletions(-) create mode 100644 .changeset/healthy-dots-ring.md create mode 100644 plugins/scaffolder-backend/migrations/20240401213200_workspace.js create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/serializer.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/serializer.ts diff --git a/.changeset/healthy-dots-ring.md b/.changeset/healthy-dots-ring.md new file mode 100644 index 0000000000..005e7e6616 --- /dev/null +++ b/.changeset/healthy-dots-ring.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch +--- + +Scaffolder workspace serialization diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 6af1ac04a6..b9bf44b34f 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -3,6 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// + import { ActionContext as ActionContext_2 } from '@backstage/plugin-scaffolder-node'; import { AuthService } from '@backstage/backend-plugin-api'; import * as azure from '@backstage/plugin-scaffolder-backend-module-azure'; @@ -370,6 +372,8 @@ export interface CurrentClaimedTask { spec: TaskSpec; state?: JsonObject; taskId: string; + // (undocumented) + workspace?: Promise; } // @public @@ -414,6 +418,8 @@ export class DatabaseTaskStore implements TaskStore { | undefined >; // (undocumented) + getWorkspace?(options: { taskId: string }): Promise; + // (undocumented) heartbeatTask(taskId: string): Promise; // (undocumented) list(options: { createdBy?: string }): Promise<{ @@ -437,6 +443,8 @@ export class DatabaseTaskStore implements TaskStore { // (undocumented) saveTaskState(options: { taskId: string; state?: JsonObject }): Promise; // (undocumented) + serializeWorkspace(options: { path: string; taskId: string }): Promise; + // (undocumented) shutdownTask(options: TaskStoreShutDownTaskOptions): Promise; } @@ -554,10 +562,14 @@ export class TaskManager implements TaskContext_2 { | undefined >; // (undocumented) + getWorkspace(options: { taskId: string }): Promise; + // (undocumented) getWorkspaceName(): Promise; // (undocumented) get secrets(): TaskSecrets_2 | undefined; // (undocumented) + serializeWorkspace?(options: { path: string }): Promise; + // (undocumented) get spec(): TaskSpecV1beta3; // (undocumented) updateCheckpoint?( @@ -609,6 +621,8 @@ export interface TaskStore { | undefined >; // (undocumented) + getWorkspace?(options: { taskId: string }): Promise; + // (undocumented) heartbeatTask(taskId: string): Promise; // (undocumented) list?(options: { createdBy?: string }): Promise<{ @@ -634,6 +648,14 @@ export interface TaskStore { state?: JsonObject; }): Promise; // (undocumented) + serializeWorkspace?({ + path, + taskId, + }: { + path: string; + taskId: string; + }): Promise; + // (undocumented) shutdownTask?(options: TaskStoreShutDownTaskOptions): Promise; } diff --git a/plugins/scaffolder-backend/migrations/20240401213200_workspace.js b/plugins/scaffolder-backend/migrations/20240401213200_workspace.js new file mode 100644 index 0000000000..175de54cd9 --- /dev/null +++ b/plugins/scaffolder-backend/migrations/20240401213200_workspace.js @@ -0,0 +1,35 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('tasks', table => { + table.binary('workspace').nullable().comment('A snapshot of the workspace'); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + await knex.schema.alterTable('tasks', table => { + table.dropColumn('workspace'); + }); +}; diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index d107d578cb..aef07c731f 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -78,6 +78,7 @@ "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", + "concat-stream": "^2.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", @@ -93,6 +94,7 @@ "p-limit": "^3.1.0", "p-queue": "^6.6.2", "prom-client": "^15.0.0", + "tar": "^6.1.12", "uuid": "^9.0.0", "winston": "^3.2.1", "winston-transport": "^4.7.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index ac391ea2b6..7c6606a1a2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -42,6 +42,7 @@ import { DateTime, Duration } from 'luxon'; import { TaskRecovery, TaskSpec } from '@backstage/plugin-scaffolder-common'; import { trimEventsTillLastRecovery } from './taskRecoveryHelper'; import { intervalFromNowTill } from './dbUtil'; +import { restoreWorkspace, serializeWorkspace } from './serializer'; const migrationsDir = resolvePackagePath( '@backstage/plugin-scaffolder-backend', @@ -57,6 +58,7 @@ export type RawDbTaskRow = { created_at: string; created_by: string | null; secrets?: string | null; + workspace?: Buffer; }; export type RawDbTaskEventRow = { @@ -509,6 +511,30 @@ export class DatabaseTaskStore implements TaskStore { }); } + async rehydrateWorkspace?(options: { + taskId: string; + targetPath: string; + }): Promise { + const [result] = await this.db('tasks') + .where({ id: options.taskId }) + .select('workspace'); + + await restoreWorkspace(options.targetPath, result.workspace); + } + + async serializeWorkspace(options: { + path: string; + taskId: string; + }): Promise { + if (options.path) { + await this.db('tasks') + .where({ id: options.taskId }) + .update({ + workspace: await serializeWorkspace(options.path), + }); + } + } + async cancelTask( options: TaskStoreEmitOptions<{ message: string } & JsonObject>, ): Promise { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 5d24ac7cc7..decb05ad88 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -419,6 +419,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { reason: stringifyError(err), }); throw err; + } finally { + await task.serializeWorkspace?.({ path: workspacePath }); } }, createTemporaryDirectory: async () => { @@ -460,6 +462,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { await taskTrack.markFailed(step, err); await stepTrack.markFailed(); throw err; + } finally { + await task.serializeWorkspace?.({ path: workspacePath }); } } @@ -469,10 +473,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { 'Wrong template version executed with the workflow engine', ); } - const workspacePath = path.join( - this.options.workingDirectory, - await task.getWorkspaceName(), - ); + const taskId = await task.getWorkspaceName(); + + const workspacePath = path.join(this.options.workingDirectory, taskId); const { additionalTemplateFilters, additionalTemplateGlobals } = this.options; @@ -486,6 +489,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { }); try { + await task.rehydrateWorkspace?.({ taskId, targetPath: workspacePath }); + const taskTrack = await this.tracker.taskStart(task); await fs.ensureDir(workspacePath); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index e9a408d9d6..51f3afc623 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -99,6 +99,13 @@ export class TaskManager implements TaskContext { return this.task.taskId; } + async rehydrateWorkspace(options: { + taskId: string; + targetPath: string; + }): Promise { + return this.storage.rehydrateWorkspace?.(options); + } + get done() { return this.isDone; } @@ -144,6 +151,13 @@ export class TaskManager implements TaskContext { }); } + async serializeWorkspace?(options: { path: string }): Promise { + await this.storage.serializeWorkspace?.({ + path: options.path, + taskId: this.task.taskId, + }); + } + async complete( result: TaskCompletionState, metadata?: JsonObject, @@ -219,6 +233,8 @@ export interface CurrentClaimedTask { * The creator of the task. */ createdBy?: string; + + workspace?: Promise; } function defer() { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/serializer.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/serializer.test.ts new file mode 100644 index 0000000000..bec5398e28 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/serializer.test.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { serializeWorkspace, restoreWorkspace } from './serializer'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import fs from 'fs-extra'; + +describe('serializer', () => { + const workspaceDir = createMockDirectory({ + content: { + 'app-config.yaml': ` + app: + title: Example App + sessionKey: + $file: secrets/session-key.txt + escaped: \$\${Escaped} + `, + 'app-config2.yaml': ` + app: + title: Example App 2 + sessionKey: + $file: secrets/session-key.txt + escaped: \$\${Escaped} + `, + 'app-config.development.yaml': ` + app: + sessionKey: development-key + backend: + $include: ./included.yaml + other: + $include: secrets/included.yaml + `, + 'secrets/session-key.txt': 'abc123', + 'secrets/included.yaml': ` + secret: + $file: session-key.txt + `, + 'included.yaml': ` + foo: + bar: token \${MY_SECRET} + `, + 'app-config.substitute.yaml': ` + app: + someConfig: + $include: \${SUBSTITUTE_ME}.yaml + noSubstitute: + $file: \$\${ESCAPE_ME}.txt + `, + 'substituted.yaml': ` + secret: + $file: secrets/\${SUBSTITUTE_ME}.txt + `, + 'secrets/substituted.txt': '123abc', + '${ESCAPE_ME}.txt': 'notSubstituted', + 'empty.yaml': '# just a comment', + }, + }); + + const restoredWorkspaceDir = createMockDirectory(); + + it('should be able to archive and restore the workspace', async () => { + const workspaceBuffer = await serializeWorkspace(workspaceDir.path); + await restoreWorkspace(restoredWorkspaceDir.path, workspaceBuffer); + + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/\$\{ESCAPE_ME\}.txt`), + ).toBeTruthy(); + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/app-config.development.yaml`), + ).toBeTruthy(); + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/app-config.substitute.yaml`), + ).toBeTruthy(); + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/app-config.yaml`), + ).toBeTruthy(); + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/app-config2.yaml`), + ).toBeTruthy(); + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/empty.yaml`), + ).toBeTruthy(); + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/included.yaml`), + ).toBeTruthy(); + expect(fs.existsSync(`${restoredWorkspaceDir.path}/secrets`)).toBeTruthy(); + expect( + fs.existsSync(`${restoredWorkspaceDir.path}/substituted.yaml`), + ).toBeTruthy(); + + expect( + fs.readFileSync(`${restoredWorkspaceDir.path}/substituted.yaml`, 'utf8'), + ).toEqual(` + secret: + $file: secrets/\${SUBSTITUTE_ME}.txt + `); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/serializer.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/serializer.ts new file mode 100644 index 0000000000..69c48fb769 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/serializer.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import tar from 'tar'; +import concatStream from 'concat-stream'; +import { promisify } from 'util'; +import { pipeline as pipelineCb, Readable } from 'stream'; + +const pipeline = promisify(pipelineCb); + +export const serializeWorkspace = async (path: string): Promise => { + return await new Promise(async resolve => { + await pipeline(tar.create({ cwd: path }, ['']), concatStream(resolve)); + }); +}; + +export const restoreWorkspace = async (path: string, buffer?: Buffer) => { + if (buffer) { + await pipeline( + Readable.from(buffer), + tar.extract({ + C: path, + }), + ); + } +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index aa647d8bd6..676b7e8c3a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -211,6 +211,19 @@ export interface TaskStore { ): Promise<{ events: SerializedTaskEvent[] }>; shutdownTask?(options: TaskStoreShutDownTaskOptions): Promise; + + rehydrateWorkspace?(options: { + taskId: string; + targetPath: string; + }): Promise; + + serializeWorkspace?({ + path, + taskId, + }: { + path: string; + taskId: string; + }): Promise; } export type WorkflowResponse = { output: { [key: string]: JsonValue } }; diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index c757301249..7211dbcb28 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -361,12 +361,16 @@ export interface TaskContext { | undefined >; // (undocumented) + getWorkspace?(options: { taskId: string }): Promise; + // (undocumented) getWorkspaceName(): Promise; // (undocumented) isDryRun?: boolean; // (undocumented) secrets?: TaskSecrets; // (undocumented) + serializeWorkspace?(options: { path: string }): Promise; + // (undocumented) spec: TaskSpec; // (undocumented) updateCheckpoint?( diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index aef2c5f360..b2da5b2f0f 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -141,6 +141,13 @@ export interface TaskContext { }, ): Promise; + serializeWorkspace?(options: { path: string }): Promise; + + rehydrateWorkspace?(options: { + taskId: string; + targetPath: string; + }): Promise; + getWorkspaceName(): Promise; getInitiatorCredentials(): Promise; diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..e9a7947795 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6788,6 +6788,7 @@ __metadata: "@types/nunjucks": ^3.1.4 "@types/supertest": ^2.0.8 "@types/zen-observable": ^0.8.0 + concat-stream: ^2.0.0 esbuild: ^0.20.0 express: ^4.17.1 express-promise-router: ^4.1.0 @@ -6806,6 +6807,7 @@ __metadata: prom-client: ^15.0.0 strip-ansi: ^7.1.0 supertest: ^6.1.3 + tar: ^6.1.12 uuid: ^9.0.0 wait-for-expect: ^3.0.2 winston: ^3.2.1 From ee538cb044075ea4dc0b4266e8f03285ee13c03b Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 30 Apr 2024 21:13:35 +0200 Subject: [PATCH 152/567] Scaffolder workspace serialization Signed-off-by: bnechyporenko --- plugins/scaffolder-backend/api-report.md | 21 +++++++++++++++------ plugins/scaffolder-node/api-report.md | 7 +++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index b9bf44b34f..38ee521b38 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -418,8 +418,6 @@ export class DatabaseTaskStore implements TaskStore { | undefined >; // (undocumented) - getWorkspace?(options: { taskId: string }): Promise; - // (undocumented) heartbeatTask(taskId: string): Promise; // (undocumented) list(options: { createdBy?: string }): Promise<{ @@ -441,6 +439,11 @@ export class DatabaseTaskStore implements TaskStore { ids: string[]; }>; // (undocumented) + rehydrateWorkspace?(options: { + taskId: string; + targetPath: string; + }): Promise; + // (undocumented) saveTaskState(options: { taskId: string; state?: JsonObject }): Promise; // (undocumented) serializeWorkspace(options: { path: string; taskId: string }): Promise; @@ -562,10 +565,13 @@ export class TaskManager implements TaskContext_2 { | undefined >; // (undocumented) - getWorkspace(options: { taskId: string }): Promise; - // (undocumented) getWorkspaceName(): Promise; // (undocumented) + rehydrateWorkspace(options: { + taskId: string; + targetPath: string; + }): Promise; + // (undocumented) get secrets(): TaskSecrets_2 | undefined; // (undocumented) serializeWorkspace?(options: { path: string }): Promise; @@ -621,8 +627,6 @@ export interface TaskStore { | undefined >; // (undocumented) - getWorkspace?(options: { taskId: string }): Promise; - // (undocumented) heartbeatTask(taskId: string): Promise; // (undocumented) list?(options: { createdBy?: string }): Promise<{ @@ -643,6 +647,11 @@ export interface TaskStore { ids: string[]; }>; // (undocumented) + rehydrateWorkspace?(options: { + taskId: string; + targetPath: string; + }): Promise; + // (undocumented) saveTaskState?(options: { taskId: string; state?: JsonObject; diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 7211dbcb28..a6b91bdccb 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -361,12 +361,15 @@ export interface TaskContext { | undefined >; // (undocumented) - getWorkspace?(options: { taskId: string }): Promise; - // (undocumented) getWorkspaceName(): Promise; // (undocumented) isDryRun?: boolean; // (undocumented) + rehydrateWorkspace?(options: { + taskId: string; + targetPath: string; + }): Promise; + // (undocumented) secrets?: TaskSecrets; // (undocumented) serializeWorkspace?(options: { path: string }): Promise; From 84cdb92346785ceffda429fa64e35d7a6bce94cf Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 30 Apr 2024 13:20:34 -0600 Subject: [PATCH 153/567] integration support for harness - fixed comments p2 Signed-off-by: Calvin Lee --- packages/integration/package.json | 1 - packages/integration/src/harness/core.test.ts | 16 ++++++++-------- packages/integration/src/harness/core.ts | 4 ---- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/integration/package.json b/packages/integration/package.json index b57b94f982..bbd0eecbe7 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -50,7 +50,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", - "@backstage/test-utils": "workspace:^", "@types/luxon": "^3.0.0", "msw": "^1.0.0" }, diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts index abfbb5d4c2..d3d402806a 100644 --- a/packages/integration/src/harness/core.test.ts +++ b/packages/integration/src/harness/core.test.ts @@ -15,7 +15,7 @@ */ import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { setupRequestMockHandlers } from '../helpers'; import { HarnessIntegrationConfig } from './config'; import { getHarnessEditContentsUrl, @@ -59,26 +59,26 @@ describe('Harness code core', () => { }); }); - describe('getGerritRequestOptions', () => { + describe('getHarnessRequestOptions', () => { it('adds token header when only a token is specified', () => { const authRequest: HarnessIntegrationConfig = { - host: 'gerrit.com', + host: 'app.harness.io', token: 'P', }; const anonymousRequest: HarnessIntegrationConfig = { - host: 'gerrit.com', + host: 'app.harness.io', }; expect( (getHarnessRequestOptions(authRequest).headers as any).Authorization, ).toEqual('Bearer P'); - expect( - getHarnessRequestOptions(anonymousRequest).headers, - ).toBeUndefined(); + expect(getHarnessRequestOptions(anonymousRequest).headers).toStrictEqual( + {}, + ); }); it('adds basic auth when apikey and token are specified', () => { const authRequest: HarnessIntegrationConfig = { - host: 'gerrit.com', + host: 'app.harness.io', token: 'P', apiKey: 'a', }; diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 8eba850ec2..3a61905e27 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -120,10 +120,6 @@ export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { const headers: Record = {}; const { token, apiKey } = config; - if (!token) { - return headers; - } - if (apiKey) { headers['x-api-key'] = apiKey; } else if (token) { From 677b10650b806d743e0f6ad1b2718fb11d8cf3c5 Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Tue, 30 Apr 2024 13:23:30 -0600 Subject: [PATCH 154/567] integration support for harness - fixed comments p2 Signed-off-by: Calvin Lee --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 60b03d4399..3cf5a79175 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4368,7 +4368,6 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" - "@backstage/test-utils": "workspace:^" "@octokit/auth-app": ^4.0.0 "@octokit/rest": ^19.0.3 "@types/luxon": ^3.0.0 From 425b403bb212a4b8fd41a0a5dfdffcc32c7e9058 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 30 Apr 2024 21:28:55 +0200 Subject: [PATCH 155/567] Scaffolder workspace serialization Signed-off-by: bnechyporenko --- .../tasks/DatabaseTaskStore.test.ts | 32 +++++++++++++++++++ .../src/scaffolder/tasks/DatabaseTaskStore.ts | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts index ab80dcff04..f4fae0715b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts @@ -19,6 +19,8 @@ import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { ConflictError } from '@backstage/errors'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import fs from 'fs-extra'; const createStore = async () => { const manager = DatabaseManager.fromConfig( @@ -37,6 +39,18 @@ const createStore = async () => { return { store, manager }; }; +const workspaceDir = createMockDirectory({ + content: { + 'app-config.yaml': ` + app: + title: Example App + sessionKey: + $file: secrets/session-key.txt + escaped: \$\${Escaped} + `, + }, +}); + describe('DatabaseTaskStore', () => { it('should create the database store and run migration', async () => { const { store, manager } = await createStore(); @@ -259,4 +273,22 @@ describe('DatabaseTaskStore', () => { }, }); }); + + it('serialize and restore the workspace', async () => { + const { store } = await createStore(); + const { taskId } = await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'me', + }); + + await store.serializeWorkspace({ path: workspaceDir.path, taskId }); + expect(fs.existsSync(`${workspaceDir.path}/app-config.yaml`)).toBeTruthy(); + + fs.removeSync(workspaceDir.path); + expect(fs.existsSync(`${workspaceDir.path}/app-config.yaml`)).toBeFalsy(); + + fs.mkdirSync(workspaceDir.path); + await store.rehydrateWorkspace({ targetPath: workspaceDir.path, taskId }); + expect(fs.existsSync(`${workspaceDir.path}/app-config.yaml`)).toBeTruthy(); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 7c6606a1a2..792b74e8d2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -511,7 +511,7 @@ export class DatabaseTaskStore implements TaskStore { }); } - async rehydrateWorkspace?(options: { + async rehydrateWorkspace(options: { taskId: string; targetPath: string; }): Promise { From 8ed3cd6fb715fbf8fd8fa9ea871beb1f058cdbd0 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Wed, 1 May 2024 07:40:33 +0200 Subject: [PATCH 156/567] Scaffolder workspace serialization Signed-off-by: bnechyporenko --- plugins/scaffolder-backend/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 38ee521b38..3ef21f4c98 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -439,7 +439,7 @@ export class DatabaseTaskStore implements TaskStore { ids: string[]; }>; // (undocumented) - rehydrateWorkspace?(options: { + rehydrateWorkspace(options: { taskId: string; targetPath: string; }): Promise; From 12a5feff7a9fd4c6e3c844f25da850aff8de77d6 Mon Sep 17 00:00:00 2001 From: Chap Ambrose Date: Tue, 30 Apr 2024 16:52:20 -0500 Subject: [PATCH 157/567] seperate ensureSchemaExists config Signed-off-by: Chap Ambrose --- .../src/database/connectors/postgres.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 1ddc096e05..1aa4d2afa9 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -321,7 +321,7 @@ export class PgConnector implements Connector { let schemaOverrides; if (this.getPluginDivisionModeConfig() === 'schema') { schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureExistsConfig(pluginId)) { + if (this.getEnsureSchemaExistsConfig(pluginId)) { try { await pgConnector.ensureSchemaExists!(pluginConfig, pluginId); } catch (error) { @@ -437,6 +437,15 @@ export class PgConnector implements Connector { ); } + private getEnsureSchemaExistsConfig(pluginId: string): boolean { + const baseConfig = + this.config.getOptionalBoolean('ensureSchemaExists') ?? true; + return ( + this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? + baseConfig + ); + } + private getPluginDivisionModeConfig(): string { return this.config.getOptionalString('pluginDivisionMode') ?? 'database'; } From 86ae51bb4ad512d9c569e192fcf5ccaa0cf8b698 Mon Sep 17 00:00:00 2001 From: Chap Ambrose Date: Wed, 1 May 2024 08:38:17 -0500 Subject: [PATCH 158/567] fixed getEnsureSchemaExistsConfig Signed-off-by: Chap Ambrose --- packages/backend-common/src/database/connectors/postgres.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 1aa4d2afa9..126c8cb89c 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -441,8 +441,9 @@ export class PgConnector implements Connector { const baseConfig = this.config.getOptionalBoolean('ensureSchemaExists') ?? true; return ( - this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? - baseConfig + this.config.getOptionalBoolean( + `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`, + ) ?? baseConfig ); } From ccc8851bd01a9f05bc4ce8e71f11a417c90cfdb4 Mon Sep 17 00:00:00 2001 From: Chap Ambrose Date: Wed, 1 May 2024 09:05:30 -0500 Subject: [PATCH 159/567] add changeset Signed-off-by: Chap Ambrose --- .changeset/heavy-trainers-fly.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/heavy-trainers-fly.md diff --git a/.changeset/heavy-trainers-fly.md b/.changeset/heavy-trainers-fly.md new file mode 100644 index 0000000000..db09f63496 --- /dev/null +++ b/.changeset/heavy-trainers-fly.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +add ensureSchemaExists backend database config From 0b8b8e80c8a6bfe74420d40c0f91fa6dcdcfeefe Mon Sep 17 00:00:00 2001 From: Chap Ambrose Date: Wed, 1 May 2024 10:07:08 -0500 Subject: [PATCH 160/567] set ensureSchemaExists to false to match current behavior Signed-off-by: Chap Ambrose --- .changeset/heavy-trainers-fly.md | 2 +- .../backend-common/src/database/connectors/postgres.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.changeset/heavy-trainers-fly.md b/.changeset/heavy-trainers-fly.md index db09f63496..48560b7ddd 100644 --- a/.changeset/heavy-trainers-fly.md +++ b/.changeset/heavy-trainers-fly.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -add ensureSchemaExists backend database config +Added config prop `ensureSchemaExists` to support postgres instances where user can create schemas but not databases. diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 126c8cb89c..b81bfd5b50 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -321,7 +321,10 @@ export class PgConnector implements Connector { let schemaOverrides; if (this.getPluginDivisionModeConfig() === 'schema') { schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureSchemaExistsConfig(pluginId)) { + if ( + this.getEnsureSchemaExistsConfig(pluginId) || + this.getEnsureExistsConfig(pluginId) + ) { try { await pgConnector.ensureSchemaExists!(pluginConfig, pluginId); } catch (error) { @@ -439,7 +442,7 @@ export class PgConnector implements Connector { private getEnsureSchemaExistsConfig(pluginId: string): boolean { const baseConfig = - this.config.getOptionalBoolean('ensureSchemaExists') ?? true; + this.config.getOptionalBoolean('ensureSchemaExists') ?? false; return ( this.config.getOptionalBoolean( `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`, From f1ee6899c91d0561ae750d5b0a82826c6458b2bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 15:23:27 +0000 Subject: [PATCH 161/567] chore(deps): bump ejs from 3.1.9 to 3.1.10 Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/mde/ejs/releases) - [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10) --- updated-dependencies: - dependency-name: ejs dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..f50de49dfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22872,13 +22872,13 @@ __metadata: linkType: hard "ejs@npm:^3.1.6": - version: 3.1.9 - resolution: "ejs@npm:3.1.9" + version: 3.1.10 + resolution: "ejs@npm:3.1.10" dependencies: jake: ^10.8.5 bin: ejs: bin/cli.js - checksum: af6f10eb815885ff8a8cfacc42c6b6cf87daf97a4884f87a30e0c3271fedd85d76a3a297d9c33a70e735b97ee632887f85e32854b9cdd3a2d97edf931519a35f + checksum: ce90637e9c7538663ae023b8a7a380b2ef7cc4096de70be85abf5a3b9641912dde65353211d05e24d56b1f242d71185c6d00e02cb8860701d571786d92c71f05 languageName: node linkType: hard From d541ff686f828c9b7a18e447e2dc81359f5918fe Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 2 May 2024 08:11:35 +0300 Subject: [PATCH 162/567] fix: email processor esm issue with p-throttle + config read Signed-off-by: Heikki Hellgren --- .changeset/cyan-eagles-hammer.md | 6 ++++++ .github/renovate.json5 | 4 ++++ .../notifications-backend-module-email/package.json | 2 +- .../processor/NotificationsEmailProcessor.test.ts | 12 ++++++------ .../src/processor/NotificationsEmailProcessor.ts | 2 +- yarn.lock | 10 +++++----- 6 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 .changeset/cyan-eagles-hammer.md diff --git a/.changeset/cyan-eagles-hammer.md b/.changeset/cyan-eagles-hammer.md new file mode 100644 index 0000000000..dfe6f0bc62 --- /dev/null +++ b/.changeset/cyan-eagles-hammer.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +'@backstage/plugin-notifications-backend': patch +--- + +Fixed email processor `esm` issue and config reading diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a9cb54912a..1c8d611c49 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -62,6 +62,10 @@ matchPackageNames: ['p-limit'], allowedVersions: '<4.0.0', }, + { + matchPackageNames: ['p-throttle'], + allowedVersions: '<4.0.0', + }, { matchPackageNames: ['p-queue'], allowedVersions: '<7.0.0', diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 94b5e10578..bb99d9cb97 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -45,7 +45,7 @@ "@backstage/types": "workspace:^", "lodash": "^4.17.21", "nodemailer": "^6.9.13", - "p-throttle": "^6.1.0" + "p-throttle": "^4.1.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 3af1e498ca..11eef9b999 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -52,7 +52,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'smtp', hostname: 'localhost', port: 465, @@ -98,7 +98,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'ses', region: 'us-west-2', }, @@ -138,7 +138,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, @@ -189,7 +189,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, @@ -246,7 +246,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, @@ -306,7 +306,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 5387d09c3d..ce944d9592 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -62,7 +62,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { const emailProcessorConfig = config.getConfig( 'notifications.processors.email', ); - this.transportConfig = emailProcessorConfig.getConfig('transport'); + this.transportConfig = emailProcessorConfig.getConfig('transportConfig'); this.broadcastConfig = emailProcessorConfig.getOptionalConfig('broadcastConfig'); this.sender = emailProcessorConfig.getString('sender'); diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..6b4def9d8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6193,7 +6193,7 @@ __metadata: "@types/nodemailer": ^6.4.14 lodash: ^4.17.21 nodemailer: ^6.9.13 - p-throttle: ^6.1.0 + p-throttle: ^4.1.1 languageName: unknown linkType: soft @@ -33449,10 +33449,10 @@ __metadata: languageName: node linkType: hard -"p-throttle@npm:^6.1.0": - version: 6.1.0 - resolution: "p-throttle@npm:6.1.0" - checksum: c1947cca8844564c3d86f8c09067add5e7398b87898cb1f1aea5c48d2c211590d039a316d28a4b0d95364ffa0213c01dff6bf71175c468eab0e381f77715dbdb +"p-throttle@npm:^4.1.1": + version: 4.1.1 + resolution: "p-throttle@npm:4.1.1" + checksum: fe8709f3c3b1da7c033479375c2c302e80c1a5d86449013afa7cd46d1dc210bc824a7e4a9d088e66d31987d00878c2b5491bb2fe76246d4d2fc9a1636f5f8298 languageName: node linkType: hard From faa86f3981845b86f1b90ce24b3fcf15f0825787 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 2 May 2024 09:57:49 +0200 Subject: [PATCH 163/567] Register the to the DevApp fixing the issue to launch locally the plugin. Add a new section to the plugin scaffolder README. #23684 Signed-off-by: cmoulliard --- .changeset/few-dodos-cheer.md | 5 +++++ plugins/scaffolder/README.md | 9 +++++++++ plugins/scaffolder/dev/index.tsx | 18 ++++++------------ 3 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 .changeset/few-dodos-cheer.md diff --git a/.changeset/few-dodos-cheer.md b/.changeset/few-dodos-cheer.md new file mode 100644 index 0000000000..a80147e112 --- /dev/null +++ b/.changeset/few-dodos-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Register the `catalogPlugin` to the DevApp fixing the issue to launch locally the plugin diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index a6af441d62..07111a33e5 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -121,6 +121,15 @@ export const apis: AnyApiFactory[] = [ This replaces the default implementation of the `scaffolderApiRef`. +### Local development + +When you develop a new template, action or new ``, then we recommend +to launch the plugin locally using the `createDevApp` of the `./dev/index.tsx` file for testing/Debugging purposes + +To play with it, open a terminal and run the command: `yarn start` within the `./plugins/scaffolder` folder + +**NOTE:** Don't forget to open a second terminal and to launch the backend or [backend-next](../../docs/backend-system/index.md) there, using `yarn start` and to specify the locations of the templates to play with ! + ## Links - [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend) diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index e4425b04bf..2980fe90fc 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -14,11 +14,9 @@ * limitations under the License. */ -import { CatalogClient } from '@backstage/catalog-client'; import { createDevApp } from '@backstage/dev-utils'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { - catalogApiRef, starredEntitiesApiRef, MockStarredEntitiesApi, } from '@backstage/plugin-catalog-react'; @@ -30,18 +28,10 @@ import { fetchApiRef, identityApiRef, } from '@backstage/core-plugin-api'; -import { CatalogEntityPage } from '@backstage/plugin-catalog'; +import { CatalogEntityPage, catalogPlugin } from '@backstage/plugin-catalog'; createDevApp() - .addPage({ - path: '/catalog/:kind/:namespace/:name', - element: , - }) - .registerApi({ - api: catalogApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new CatalogClient({ discoveryApi }), - }) + .registerPlugin(catalogPlugin) .registerApi({ api: starredEntitiesApiRef, deps: {}, @@ -63,6 +53,10 @@ createDevApp() identityApi, }), }) + .addPage({ + path: '/catalog/:kind/:namespace/:name', + element: , + }) .addPage({ path: '/create', title: 'Create', From 6a8e728cf2686dbde09c0b58eb66e53ccbf76c4f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 2 May 2024 13:38:53 +0200 Subject: [PATCH 164/567] docs/architecture-overview: update to include new backend system Signed-off-by: Patrik Oldsberg --- .../package-architecture.drawio.svg | 663 ++++++++++-------- 1 file changed, 369 insertions(+), 294 deletions(-) diff --git a/docs/assets/architecture-overview/package-architecture.drawio.svg b/docs/assets/architecture-overview/package-architecture.drawio.svg index df8c0f8820..89fab2647d 100644 --- a/docs/assets/architecture-overview/package-architecture.drawio.svg +++ b/docs/assets/architecture-overview/package-architecture.drawio.svg @@ -1,69 +1,66 @@ - + - - - - - - - - - - + + + + + + + + + - +
-
-
+
+
app
- + app - - - - - - + + + + - +
-
-
+
+
backend
- + backend - - - - - - + + + + + + - +
-
-
+
+
plugin-<plugin-id>
@@ -74,19 +71,19 @@ - - - - - - + + + + + + - +
-
-
+
+
plugin-<plugin-id>-backend
@@ -97,69 +94,9 @@ - - - - - - Backend Libraries - - - - - - -
-
-
- @backstage/backend-common -
-
-
-
- - @backstage/backend-common - -
-
- - - - -
-
-
- @backstage/backend-test-utils -
-
-
-
- - @backstage/backend-test-utils - -
-
- - - - -
-
-
- @backstage/backend-tasks -
-
-
-
- - @backstage/backend-tasks - -
-
- - - - + + + Common Libraries @@ -167,16 +104,16 @@ - +
-
-
+
+
@backstage/catalog-client
- + @backstage/catalog-client @@ -184,16 +121,16 @@ - +
-
-
+
+
@backstage/types
- + @backstage/types @@ -201,16 +138,16 @@ - +
-
-
+
+
@backstage/config
- + @backstage/config @@ -218,16 +155,16 @@ - +
-
-
+
+
@backstage/errors
- + @backstage/errors @@ -235,16 +172,16 @@ - +
-
-
+
+
@backstage/catalog-model
- + @backstage/catalog-model @@ -252,24 +189,23 @@ - +
-
-
+
+
@backstage/integration
- + @backstage/integration - - - - + + + Frontend App Core @@ -277,16 +213,16 @@ - +
-
-
+
+
@backstage/core-app-api
- + @backstage/core-app-api @@ -294,36 +230,36 @@ - +
-
-
+
+
@backstage/app-defaults
- + @backstage/app-defaults - - - - - - - - - + + + + + + + + + - +
-
-
+
+
plugin-<plugin-id>-backend-module-<module-id>
@@ -334,17 +270,17 @@ - - - - + + + + - +
-
-
+
+
plugin-<plugin-id>-module-<module-id>
@@ -355,10 +291,9 @@ - - - - + + + Common Tooling @@ -366,28 +301,27 @@ - +
-
-
+
+
@backstage/cli
- + @backstage/cli - - - - - - - - + + + + + + + External Plugin Libraries @@ -395,16 +329,16 @@ - +
-
-
+
+
plugin-<other-plugin-id>-react
- + plugin-<other-plugin-id>-react @@ -412,16 +346,16 @@ - +
-
-
+
+
plugin-<other-plugin-id>-common
- + plugin-<other-plugin-id>-common @@ -429,30 +363,29 @@ - +
-
-
+
+
plugin-<other-plugin-id>-node
- + plugin-<other-plugin-id>-node - - - - - - - - - - + + + + + + + + + Plugin Libraries @@ -460,10 +393,10 @@ - +
-
-
+
+
plugin-<plugin-id>-react
@@ -477,10 +410,10 @@ - +
-
-
+
+
plugin-<plugin-id>-common
@@ -494,10 +427,10 @@ - +
-
-
+
+
plugin-<plugin-id>-node
@@ -508,13 +441,11 @@ - - - - - - - + + + + + Frontend Plugin Core @@ -522,16 +453,16 @@ - +
-
-
+
+
@backstage/core-plugin-api
- + @backstage/core-plugin-api @@ -539,16 +470,16 @@ - +
-
-
+
+
@backstage/test-utils
- + @backstage/test-utils @@ -556,24 +487,23 @@ - +
-
-
+
+
@backstage/dev-utils
- + @backstage/dev-utils - - - - + + + Frontend Libraries @@ -581,16 +511,16 @@ - +
-
-
+
+
@backstage/integration-react
- + @backstage/integration-react @@ -598,16 +528,16 @@ - +
-
-
+
+
@backstage/core-components
- + @backstage/core-components @@ -615,131 +545,276 @@ - +
-
-
+
+
@backstage/theme
- + @backstage/theme - - - - + + + + - -
-
-
+ +
+
+
Frontend Package
- + Frontend Package - -
-
-
+ +
+
+
Isomorphic Package
- + Isomorphic Package - -
-
-
+ +
+
+
Backend Package
- + Backend Package - + - -
-
-
+ +
+
+
CLI Package
- + CLI Package - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - -
-
-
+ +
+
+
Compatibility
- + Compatibility - - - - + + + + + + + + Backend App Core + + + + + + +
+
+
+ @backstage/backend-app-api +
+
+
+
+ + @backstage/backend-app-api + +
+
+ + + + +
+
+
+ @backstage/backend-defaults +
+
+
+
+ + @backstage/backend-defaults + +
+
+ + + + + + + + + Backend Plugin Core + + + + + + +
+
+
+ @backstage/backend-plugin-api +
+
+
+
+ + @backstage/backend-plugin-api + +
+
+ + + + +
+
+
+ @backstage/backend-test-utils +
+
+
+
+ + @backstage/backend-test-utils + +
+
+ + + + +
+
+
+ @backstage/backend-dev-utils +
+
+
+
+ + @backstage/backend-dev-utils + +
+
+ + + + + Backend Libraries + + + + + + +
+
+
+ @backstage/backend-tasks +
+
+
+
+ + @backstage/backend-tasks + +
+
+ + + + +
+
+
+ @backstage/backend-openapi-utils +
+
+
+
+ + @backstage/backend-openapi-utils + +
+
+ + - Viewer does not support full SVG 1.1 + Text is not SVG - cannot display From ca7ba6694d65392e36a04fa4ffe61ae2dc6e29ec Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 2 May 2024 13:39:11 +0200 Subject: [PATCH 165/567] docs/versioning-policy: update to include new backend system Signed-off-by: Patrik Oldsberg --- docs/overview/versioning-policy.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index 0b1bc975bd..2ae73e1383 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -81,19 +81,21 @@ In order for Backstage to function properly the following versioning rules must be followed. The rules are referring to the [Package Architecture](https://backstage.io/docs/overview/architecture-overview#package-architecture). -- The versions of all the packages in the `Frontend App Core` must be from the - same release, and it is recommended to keep `Common Tooling` on that release - too. -- The Backstage dependencies of any given plugin should be from the same - release. This includes the packages from `Common Libraries`, - `Frontend Plugin Core`, and `Frontend Libraries`, or alternatively the - `Backend Libraries`. -- There must be no package that is from a newer release than the - `Frontend App Core` packages in the app. +- The versions of all packages for each of the "App Core" groups must be from the + same Backstage release. +- For each frontend and backend setup, the "App Core" packages must be ahead of or on the same Backstage release as the "Plugin Core" packages, including transitive dependencies of all installed plugins and modules. +- For any given plugin, the versions of all packages from the "Plugin Core" and + "Library" groups must be from the same Backstage release. - Frontend plugins with a corresponding backend plugin should be from the same release. The update to the backend plugin **MUST** be deployed before or together with the update to the frontend plugin. +It is allowed and often expected that the "Plugin Core" and "Library" packages +are from older releases than the "App Core" packages. It is also allowed to have +duplicate installations of the "Plugin Core" and "Library" packages. This is all +to make sure that upgrading Backstage is as smooth as possible and allows for +more flexibility across the entire plugin ecosystem. + ## Package Versioning Policy Every individual package is versioned according to [semver](https://semver.org). From 503d769eb972b9ad96ee9e941ae446b3608c34fd Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 2 May 2024 13:12:13 +0300 Subject: [PATCH 166/567] feat: add new scaffolder action to send notifications Signed-off-by: Heikki Hellgren --- .changeset/new-poets-promise.md | 5 + .github/CODEOWNERS | 1 + packages/backend/package.json | 1 + .../.eslintrc.js | 1 + .../README.md | 5 + .../api-report.md | 32 ++++ .../catalog-info.yaml | 10 ++ .../package.json | 45 ++++++ .../src/actions/index.ts | 16 ++ .../src/actions/sendNotification.test.ts | 93 +++++++++++ .../src/actions/sendNotification.ts | 146 ++++++++++++++++++ .../src/index.ts | 23 +++ .../src/module.ts | 39 +++++ yarn.lock | 16 ++ 14 files changed, 433 insertions(+) create mode 100644 .changeset/new-poets-promise.md create mode 100644 plugins/scaffolder-backend-module-notifications/.eslintrc.js create mode 100644 plugins/scaffolder-backend-module-notifications/README.md create mode 100644 plugins/scaffolder-backend-module-notifications/api-report.md create mode 100644 plugins/scaffolder-backend-module-notifications/catalog-info.yaml create mode 100644 plugins/scaffolder-backend-module-notifications/package.json create mode 100644 plugins/scaffolder-backend-module-notifications/src/actions/index.ts create mode 100644 plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts create mode 100644 plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts create mode 100644 plugins/scaffolder-backend-module-notifications/src/index.ts create mode 100644 plugins/scaffolder-backend-module-notifications/src/module.ts diff --git a/.changeset/new-poets-promise.md b/.changeset/new-poets-promise.md new file mode 100644 index 0000000000..206bf3e0d8 --- /dev/null +++ b/.changeset/new-poets-promise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +--- + +Add a new scaffolder action to allow sending notifications from templates diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9fc63fbd04..9cd3bc0690 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -61,6 +61,7 @@ yarn.lock @backstage/maintainers @backst /plugins/linguist-common @backstage/maintainers @backstage/reviewers @awanlin /plugins/notifications @backstage/maintainers @backstage/notifications-maintainers /plugins/notifications-* @backstage/maintainers @backstage/notifications-maintainers +/plugins/scaffolder-backend-module-notifications @backstage/maintainers @backstage/notifications-maintainers /plugins/octopus-deploy @backstage/maintainers @backstage/reviewers @jmezach /plugins/permission-* @backstage/permission-maintainers /plugins/playlist @backstage/maintainers @backstage/reviewers @kuangp diff --git a/packages/backend/package.json b/packages/backend/package.json index ea37121492..7f42df6681 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -50,6 +50,7 @@ "@backstage/plugin-scaffolder-backend": "workspace:^", "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^", "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^", "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^", "@backstage/plugin-search-backend": "workspace:^", "@backstage/plugin-search-backend-module-catalog": "workspace:^", diff --git a/plugins/scaffolder-backend-module-notifications/.eslintrc.js b/plugins/scaffolder-backend-module-notifications/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/scaffolder-backend-module-notifications/README.md b/plugins/scaffolder-backend-module-notifications/README.md new file mode 100644 index 0000000000..49a363a994 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-scaffolder-backend-module-notifications + +The notifications backend module for the scaffolder plugin. + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/scaffolder-backend-module-notifications/api-report.md b/plugins/scaffolder-backend-module-notifications/api-report.md new file mode 100644 index 0000000000..8b12210fe9 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/api-report.md @@ -0,0 +1,32 @@ +## API Report File for "@backstage/plugin-scaffolder-backend-module-notifications" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; +import { NotificationService } from '@backstage/plugin-notifications-node'; +import { NotificationSeverity } from '@backstage/plugin-notifications-common'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; + +// @public (undocumented) +export function createSendNotificationAction(options: { + notifications: NotificationService; +}): TemplateAction< + { + recipients: string; + entityRefs?: string[] | undefined; + title: string; + info?: string | undefined; + link?: string | undefined; + severity?: NotificationSeverity | undefined; + scope?: string | undefined; + optional?: boolean | undefined; + }, + JsonObject +>; + +// @public +const scaffolderModuleNotifications: () => BackendFeature; +export default scaffolderModuleNotifications; +``` diff --git a/plugins/scaffolder-backend-module-notifications/catalog-info.yaml b/plugins/scaffolder-backend-module-notifications/catalog-info.yaml new file mode 100644 index 0000000000..640cd1ad6f --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-notifications + title: '@backstage/plugin-scaffolder-backend-module-notifications' + description: The notifications module for @backstage/plugin-scaffolder-backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json new file mode 100644 index 0000000000..11974049a1 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -0,0 +1,45 @@ +{ + "name": "@backstage/plugin-scaffolder-backend-module-notifications", + "version": "0.0.0", + "description": "The notifications backend module for the scaffolder plugin.", + "backstage": { + "role": "backend-plugin-module" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/scaffolder-backend-module-notifications" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/plugin-notifications-common": "workspace:^", + "@backstage/plugin-notifications-node": "workspace:^", + "@backstage/plugin-scaffolder-node": "workspace:^", + "octokit": "^3.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" + } +} diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/index.ts b/plugins/scaffolder-backend-module-notifications/src/actions/index.ts new file mode 100644 index 0000000000..9d708282dd --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/src/actions/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { createSendNotificationAction } from './sendNotification'; diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts new file mode 100644 index 0000000000..efd1377cae --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createSendNotificationAction } from './sendNotification'; +import { NotificationService } from '@backstage/plugin-notifications-node'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; + +describe('notification:send', () => { + const notificationService: jest.Mocked = { + send: jest.fn(), + }; + + let action: TemplateAction; + + beforeEach(() => { + jest.resetAllMocks(); + action = createSendNotificationAction({ + notifications: notificationService, + }); + }); + + const mockContext = createMockActionContext({ + input: { + recipients: 'broadcast', + title: 'Test notification', + }, + }); + + it('should send broadcast notification', async () => { + const ctx = Object.assign({}, mockContext, { + input: { recipients: 'broadcast', title: 'Test notification' }, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + title: 'Test notification', + }, + }); + }); + + it('should send entity notification', async () => { + const ctx = Object.assign({}, mockContext, { + input: { + recipients: 'entity', + entityRefs: ['user:default/john.doe'], + title: 'Test notification', + }, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'entity', entityRef: ['user:default/john.doe'] }, + payload: { + title: 'Test notification', + }, + }); + }); + + it('should throw error if entity refs are missing', async () => { + const ctx = Object.assign({}, mockContext, { + input: { + recipients: 'entity', + title: 'Test notification', + }, + }); + await expect(action.handler(ctx)).rejects.toThrow(); + }); + + it('should not throw error if entity refs are missing but optional is true', async () => { + const ctx = Object.assign({}, mockContext, { + input: { + recipients: 'entity', + title: 'Test notification', + optional: true, + }, + }); + await expect(action.handler(ctx)).resolves.not.toThrow(); + expect(notificationService.send).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts new file mode 100644 index 0000000000..f50e178174 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts @@ -0,0 +1,146 @@ +/* + * 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 { + NotificationRecipients, + NotificationService, +} from '@backstage/plugin-notifications-node'; +import { + NotificationPayload, + NotificationSeverity, +} from '@backstage/plugin-notifications-common'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; + +/** + * @public + */ +export function createSendNotificationAction(options: { + notifications: NotificationService; +}) { + const { notifications } = options; + return createTemplateAction<{ + recipients: string; + entityRefs?: string[]; + title: string; + info?: string; + link?: string; + severity?: NotificationSeverity; + scope?: string; + optional?: boolean; + }>({ + id: 'notification:send', + description: 'Sends a notification using NotificationService', + schema: { + input: { + type: 'object', + required: ['recipients', 'title'], + properties: { + recipients: { + title: 'Recipient', + enum: ['broadcast', 'entity'], + description: + 'The recipient of the notification, either broadcast or entity. If using entity, also entityRef must be provided', + type: 'string', + }, + entityRefs: { + title: 'Entity references', + description: + 'The entity references to send the notification to, required if using recipient of entity', + type: 'array', + items: { + type: 'string', + }, + }, + title: { + title: 'Title', + description: 'Notification title', + type: 'string', + }, + info: { + title: 'Description', + description: 'Notification description', + type: 'string', + }, + link: { + title: 'Link', + description: 'Notification link', + type: 'string', + }, + severity: { + title: 'Severity', + type: 'string', + description: `Notification severity`, + enum: ['low', 'normal', 'high', 'critical'], + }, + scope: { + title: 'Scope', + description: 'Notification scope', + type: 'string', + }, + optional: { + title: 'Optional', + description: + 'Do not fail the action if the notification sending fails', + type: 'boolean', + }, + }, + }, + }, + async handler(ctx) { + const { + recipients, + entityRefs, + title, + info, + link, + severity, + scope, + optional, + } = ctx.input; + + ctx.logger.info(`Sending notification to ${recipients}`); + if (recipients === 'entity' && !entityRefs) { + if (optional !== true) { + throw new Error('Entity references must be provided'); + } + return; + } + + const notificationRecipients: NotificationRecipients = + recipients === 'broadcast' + ? { type: 'broadcast' } + : { type: 'entity', entityRef: entityRefs! }; + const payload: NotificationPayload = { + title, + description: info, + link, + severity, + scope, + }; + + try { + await notifications.send({ + recipients: notificationRecipients, + payload, + }); + } catch (e) { + ctx.logger.error(`Failed to send notification: ${e}`); + if (optional !== true) { + throw e; + } + } + }, + }); +} diff --git a/plugins/scaffolder-backend-module-notifications/src/index.ts b/plugins/scaffolder-backend-module-notifications/src/index.ts new file mode 100644 index 0000000000..05a356cc71 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The notifications backend module for the scaffolder plugin. + * + * @packageDocumentation + */ +export * from './actions'; +export { scaffolderModuleNotifications as default } from './module'; diff --git a/plugins/scaffolder-backend-module-notifications/src/module.ts b/plugins/scaffolder-backend-module-notifications/src/module.ts new file mode 100644 index 0000000000..e8b6823e99 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/src/module.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { notificationService } from '@backstage/plugin-notifications-node'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createSendNotificationAction } from './actions'; + +/** + * @public + * The Notifications module for the Scaffolder Backend + */ +export const scaffolderModuleNotifications = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'notifications', + register(reg) { + reg.registerInit({ + deps: { + notifications: notificationService, + scaffolder: scaffolderActionsExtensionPoint, + }, + async init({ notifications, scaffolder }) { + scaffolder.addActions(createSendNotificationAction({ notifications })); + }, + }); + }, +}); diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..f46996d52f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6695,6 +6695,21 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-scaffolder-backend-module-notifications@workspace:^, @backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications": + version: 0.0.0-use.local + resolution: "@backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications" + dependencies: + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/plugin-notifications-common": "workspace:^" + "@backstage/plugin-notifications-node": "workspace:^" + "@backstage/plugin-scaffolder-node": "workspace:^" + "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" + octokit: ^3.0.0 + languageName: unknown + linkType: soft + "@backstage/plugin-scaffolder-backend-module-rails@workspace:^, @backstage/plugin-scaffolder-backend-module-rails@workspace:plugins/scaffolder-backend-module-rails": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-rails@workspace:plugins/scaffolder-backend-module-rails" @@ -24249,6 +24264,7 @@ __metadata: "@backstage/plugin-scaffolder-backend": "workspace:^" "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^" "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^" + "@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^" "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^" "@backstage/plugin-search-backend": "workspace:^" "@backstage/plugin-search-backend-module-catalog": "workspace:^" From 483a4f9425336091f53f79a6d6037a12dfba3f01 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 2 May 2024 13:59:48 +0200 Subject: [PATCH 167/567] microsite/data: update plugin link Signed-off-by: Patrik Oldsberg --- microsite/data/plugins/betterscan.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/betterscan.yaml b/microsite/data/plugins/betterscan.yaml index 92d1eaa7b2..852ae0081c 100644 --- a/microsite/data/plugins/betterscan.yaml +++ b/microsite/data/plugins/betterscan.yaml @@ -4,7 +4,7 @@ author: Marcin Kozlowski authorUrl: https://betterscan.io category: Security description: View security scanned vulnerabilities in Code and Cloud scanned using Open Source and proprietary scanners directly in Backstage. -documentation: https://github.com/marcinguy/betterscan-ce +documentation: https://www.npmjs.com/package/@marcinguy/backstage-plugin-betterscan iconUrl: https://uploads-ssl.webflow.com/6339e3b81867539b5fe2498d/633a1643dcb06d3029867161_g4.svg npmPackageName: '@marcinguy/backstage-plugin-betterscan' addedDate: '2022-12-08' From 9814aa6dd420d28075a68f0423e800c5100a527f Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 1 May 2024 07:43:34 -0500 Subject: [PATCH 168/567] Updated GitHub Integrations Docs Signed-off-by: Andre Wanlin --- docs/integrations/github/discovery--old.md | 376 +++++++++++++++++++++ docs/integrations/github/discovery.md | 175 +--------- docs/integrations/github/org--old.md | 368 ++++++++++++++++++++ docs/integrations/github/org.md | 332 ++++++------------ 4 files changed, 858 insertions(+), 393 deletions(-) create mode 100644 docs/integrations/github/discovery--old.md create mode 100644 docs/integrations/github/org--old.md diff --git a/docs/integrations/github/discovery--old.md b/docs/integrations/github/discovery--old.md new file mode 100644 index 0000000000..b94cb256c4 --- /dev/null +++ b/docs/integrations/github/discovery--old.md @@ -0,0 +1,376 @@ +--- +id: discovery--old +title: GitHub Discovery +sidebar_label: Discovery +# prettier-ignore +description: Automatically discovering catalog entities from repositories in a GitHub organization +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +## GitHub Provider + +The GitHub integration has a discovery provider for discovering catalog +entities within a GitHub organization. The provider will crawl the GitHub +organization and register entities matching the configured path. This can be +useful as an alternative to static locations or manually adding things to the +catalog. This is the preferred method for ingesting entities into the catalog. + +## Installation without Events Support + +You will have to add the provider in the catalog initialization code of your +backend. They are not installed by default, therefore you have to add a +dependency on `@backstage/plugin-catalog-backend-module-github` to your backend +package. + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github +``` + +And then add the entity provider to your catalog builder: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + /* highlight-add-start */ + builder.addEntityProvider( + GithubEntityProvider.fromConfig(env.config, { + logger: env.logger, + scheduler: env.scheduler, + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +## Installation with Events Support + +_For the legacy backend system, please read the sub-section below._ + +The catalog module for GitHub comes with events support enabled. +This will make it subscribe to its relevant topics (`github.push`) +and expects these events to be published via the `EventsService`. + +Additionally, you should install the +[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md) +which will route received events from the generic topic `github` to more specific ones +based on the event type (e.g., `github.push`). + +In order to receive Webhook events by GitHub, you have to decide how you want them +to be ingested into Backstage and published to its `EventsService`. +You can decide between the following options (extensible): + +- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) +- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) + +### Legacy Backend System + +Please follow the installation instructions at + +- +- + +Additionally, you need to decide how you want to receive events from external sources like + +- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) +- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) + +Set up your provider + +```ts title="packages/backend/src/plugins/catalog.ts" +import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +/* highlight-add-next-line */ +import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; +import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + builder.addProcessor(new ScaffolderEntitiesProcessor()); + /* highlight-add-start */ + const githubProvider = GithubEntityProvider.fromConfig(env.config, { + events: env.events, + logger: env.logger, + scheduler: env.scheduler, + }); + builder.addEntityProvider(githubProvider); + /* highlight-add-end */ + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; +} +``` + +You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `push` events. + +## Configuration + +To use the discovery provider, you'll need a GitHub integration +[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). For Personal Access Tokens you should pay attention to the [required scopes](https://backstage.io/docs/integrations/github/locations/#token-scopes), where you will need at least the `repo` scope for reading components. For GitHub Apps you will need to grant it the [required permissions](https://backstage.io/docs/integrations/github/github-apps#app-permissions) instead, where you will need at least the `Contents: Read-only` permissions for reading components. + +Then you can add a `github` config to the catalog providers configuration: + +```yaml +catalog: + providers: + github: + # the provider ID can be any camelCase string + providerId: + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string + filters: + branch: 'main' # string + repository: '.*' # Regex + schedule: # same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } + customProviderId: + organization: 'new-org' # string + catalogPath: '/custom/path/catalog-info.yaml' # string + filters: # optional filters + branch: 'develop' # optional string + repository: '.*' # optional Regex + wildcardProviderId: + organization: 'new-org' # string + catalogPath: '/groups/**/*.yaml' # this will search all folders for files that end in .yaml + filters: # optional filters + branch: 'develop' # optional string + repository: '.*' # optional Regex + topicProviderId: + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string + filters: + branch: 'main' # string + repository: '.*' # Regex + topic: 'backstage-exclude' # optional string + topicFilterProviderId: + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string + filters: + branch: 'main' # string + repository: '.*' # Regex + topic: + include: ['backstage-include'] # optional array of strings + exclude: ['experiments'] # optional array of strings + validateLocationsExist: + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string + filters: + branch: 'main' # string + repository: '.*' # Regex + validateLocationsExist: true # optional boolean + visibilityProviderId: + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string + filters: + visibility: + - public + - internal + enterpriseProviderId: + host: ghe.example.net + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string +``` + +This provider supports multiple organizations via unique provider IDs. + +> **Note:** It is possible but certainly not recommended to skip the provider ID level. +> If you do so, `default` will be used as provider ID. + +- **`catalogPath`** _(optional)_: + Default: `/catalog-info.yaml`. + Path where to look for `catalog-info.yaml` files. + You can use wildcards - `*` or `**` - to search the path and/or the filename. + Wildcards cannot be used if the `validateLocationsExist` option is set to `true`. +- **`filters`** _(optional)_: + - **`branch`** _(optional)_: + String used to filter results based on the branch name. + - **`repository`** _(optional)_: + Regular expression used to filter results based on the repository name. + - **`topic`** _(optional)_: + Both of the filters below may be used at the same time but the exclusion filter has the highest priority. + In the example above, a repository with the `backstage-include` topic would still be excluded + if it were also carrying the `experiments` topic. + - **`include`** _(optional)_: + An array of strings used to filter in results based on their associated GitHub topics. + If configured, only repositories with one (or more) topic(s) present in the inclusion filter will be ingested + - **`exclude`** _(optional)_: + An array of strings used to filter out results based on their associated GitHub topics. + If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested. + - **`visibility`** _(optional)_: + An array of strings used to filter results based on their visibility. Available options are `private`, `internal`, `public`. If configured (non empty), only repositories with visibility present in the filter will be ingested +- **`host`** _(optional)_: + The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). +- **`organization`**: + Name of your organization account/workspace. + If you want to add multiple organizations, you need to add one provider config each. +- **`validateLocationsExist`** _(optional)_: + Whether to validate locations that exist before emitting them. + This option avoids generating locations for catalog info files that do not exist in the source repository. + Defaults to `false`. + Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in + conjunction with wildcards in the `catalogPath`. +- **`schedule`**: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. + +## GitHub API Rate Limits + +GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise +accounts). The snippet below refreshes the Backstage catalog data every 35 minutes, which issues an API request for each discovered location. + +If your requests are too frequent then you may get throttled by +rate limiting. You can change the refresh frequency of the catalog in your `app-config.yaml` file by controlling the `schedule`. + +```yaml +schedule: + frequency: { minutes: 35 } + timeout: { minutes: 3 } +``` + +More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page. + +Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication +which carries a much higher rate limit at GitHub. + +This is true for any method of adding GitHub entities to the catalog, but +especially easy to hit with automatic discovery. + +## GitHub Processor (To Be Deprecated) + +The GitHub integration has a special discovery processor for discovering catalog +entities within a GitHub organization. The processor will crawl the GitHub +organization and register entities matching the configured path. This can be +useful as an alternative to static locations or manually adding things to the +catalog. + +## Installation + +You will have to add the processors in the catalog initialization code of your +backend. They are not installed by default, therefore you have to add a +dependency on `@backstage/plugin-catalog-backend-module-github` to your backend +package, plus `@backstage/integration` for the basic credentials management: + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/integration @backstage/plugin-catalog-backend-module-github +``` + +And then add the processors to your catalog builder: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-start */ +import { + GithubDiscoveryProcessor, + GithubOrgReaderProcessor, +} from '@backstage/plugin-catalog-backend-module-github'; +import { + ScmIntegrations, + DefaultGithubCredentialsProvider, +} from '@backstage/integration'; +/* highlight-add-end */ + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + /* highlight-add-start */ + const integrations = ScmIntegrations.fromConfig(env.config); + const githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + builder.addProcessor( + GithubDiscoveryProcessor.fromConfig(env.config, { + logger: env.logger, + githubCredentialsProvider, + }), + GithubOrgReaderProcessor.fromConfig(env.config, { + logger: env.logger, + githubCredentialsProvider, + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +## Configuration + +To use the discovery processor, you'll need a GitHub integration +[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). + +Then you can add a location target to the catalog configuration: + +```yaml +catalog: + locations: + # (since 0.13.5) Scan all repositories for a catalog-info.yaml in the root of the default branch + - type: github-discovery + target: https://github.com/myorg + # Or use a custom pattern for a subset of all repositories with default repository + - type: github-discovery + target: https://github.com/myorg/service-*/blob/-/catalog-info.yaml + # Or use a custom file format and location + - type: github-discovery + target: https://github.com/*/blob/-/docs/your-own-format.yaml + # Or use a specific branch-name + - type: github-discovery + target: https://github.com/*/blob/backstage-docs/catalog-info.yaml +``` + +Note the `github-discovery` type, as this is not a regular `url` processor. + +When using a custom pattern, the target is composed of three parts: + +- The base organization URL, `https://github.com/myorg` in this case +- The repository blob to scan, which accepts \* wildcard tokens. This can simply + be `*` to scan all repositories in the organization. This example only looks + for repositories prefixed with `service-`. +- The path within each repository to find the catalog YAML file. This will + usually be `/blob/main/catalog-info.yaml`, `/blob/master/catalog-info.yaml` or + a similar variation for catalog files stored in the root directory of each + repository. You could also use a dash (`-`) for referring to the default + branch. + +## GitHub API Rate Limits + +GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise +accounts). The default Backstage catalog backend refreshes data every 100 +seconds, which issues an API request for each discovered location. + +This means if you have more than ~140 catalog entities, you may get throttled by +rate limiting. You can change the refresh rate of the catalog in your `packages/backend/src/plugins/catalog.ts` file: + +```typescript +const builder = await CatalogBuilder.create(env); + +// For example, to refresh every 5 minutes (300 seconds). +builder.setProcessingIntervalSeconds(300); +``` + +Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication +which carries a much higher rate limit at GitHub. + +This is true for any method of adding GitHub entities to the catalog, but +especially easy to hit with automatic discovery. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index d0721e06f6..f16d1c17b2 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -6,6 +6,10 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from repositories in a GitHub organization --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + ## GitHub Provider The GitHub integration has a discovery provider for discovering catalog @@ -14,10 +18,9 @@ organization and register entities matching the configured path. This can be useful as an alternative to static locations or manually adding things to the catalog. This is the preferred method for ingesting entities into the catalog. -## Installation without Events Support +## Installation -You will have to add the provider in the catalog initialization code of your -backend. They are not installed by default, therefore you have to add a +You will have to add the GitHub Entity provider to your backend as it is not installed by default, therefore you have to add a dependency on `@backstage/plugin-catalog-backend-module-github` to your backend package. @@ -29,13 +32,12 @@ yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github And then update your backend by adding the following line: ```ts title="packages/backend/src/index.ts" -// github discovery +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ backend.add(import('@backstage/plugin-catalog-backend-module-github/alpha')); ``` -## Installation with Events Support - -_For the legacy backend system, please read the sub-section below._ +## Events Support The catalog module for GitHub comes with events support enabled. This will make it subscribe to its relevant topics (`github.push`) @@ -53,47 +55,6 @@ You can decide between the following options (extensible): - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) -### Legacy Backend System - -Please follow the installation instructions at - -- -- - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const githubProvider = GithubEntityProvider.fromConfig(env.config, { - events: env.events, - logger: env.logger, - scheduler: env.scheduler, - }); - builder.addEntityProvider(githubProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `push` events. ## Configuration @@ -236,121 +197,3 @@ which carries a much higher rate limit at GitHub. This is true for any method of adding GitHub entities to the catalog, but especially easy to hit with automatic discovery. - -## GitHub Processor (To Be Deprecated) - -The GitHub integration has a special discovery processor for discovering catalog -entities within a GitHub organization. The processor will crawl the GitHub -organization and register entities matching the configured path. This can be -useful as an alternative to static locations or manually adding things to the -catalog. - -## Installation - -You will have to add the processors in the catalog initialization code of your -backend. They are not installed by default, therefore you have to add a -dependency on `@backstage/plugin-catalog-backend-module-github` to your backend -package, plus `@backstage/integration` for the basic credentials management: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/integration @backstage/plugin-catalog-backend-module-github -``` - -And then add the processors to your catalog builder: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-start */ -import { - GithubDiscoveryProcessor, - GithubOrgReaderProcessor, -} from '@backstage/plugin-catalog-backend-module-github'; -import { - ScmIntegrations, - DefaultGithubCredentialsProvider, -} from '@backstage/integration'; -/* highlight-add-end */ - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - const integrations = ScmIntegrations.fromConfig(env.config); - const githubCredentialsProvider = - DefaultGithubCredentialsProvider.fromIntegrations(integrations); - builder.addProcessor( - GithubDiscoveryProcessor.fromConfig(env.config, { - logger: env.logger, - githubCredentialsProvider, - }), - GithubOrgReaderProcessor.fromConfig(env.config, { - logger: env.logger, - githubCredentialsProvider, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Configuration - -To use the discovery processor, you'll need a GitHub integration -[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). - -Then you can add a location target to the catalog configuration: - -```yaml -catalog: - locations: - # (since 0.13.5) Scan all repositories for a catalog-info.yaml in the root of the default branch - - type: github-discovery - target: https://github.com/myorg - # Or use a custom pattern for a subset of all repositories with default repository - - type: github-discovery - target: https://github.com/myorg/service-*/blob/-/catalog-info.yaml - # Or use a custom file format and location - - type: github-discovery - target: https://github.com/*/blob/-/docs/your-own-format.yaml - # Or use a specific branch-name - - type: github-discovery - target: https://github.com/*/blob/backstage-docs/catalog-info.yaml -``` - -Note the `github-discovery` type, as this is not a regular `url` processor. - -When using a custom pattern, the target is composed of three parts: - -- The base organization URL, `https://github.com/myorg` in this case -- The repository blob to scan, which accepts \* wildcard tokens. This can simply - be `*` to scan all repositories in the organization. This example only looks - for repositories prefixed with `service-`. -- The path within each repository to find the catalog YAML file. This will - usually be `/blob/main/catalog-info.yaml`, `/blob/master/catalog-info.yaml` or - a similar variation for catalog files stored in the root directory of each - repository. You could also use a dash (`-`) for referring to the default - branch. - -## GitHub API Rate Limits - -GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise -accounts). The default Backstage catalog backend refreshes data every 100 -seconds, which issues an API request for each discovered location. - -This means if you have more than ~140 catalog entities, you may get throttled by -rate limiting. You can change the refresh rate of the catalog in your `packages/backend/src/plugins/catalog.ts` file: - -```typescript -const builder = await CatalogBuilder.create(env); - -// For example, to refresh every 5 minutes (300 seconds). -builder.setProcessingIntervalSeconds(300); -``` - -Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication -which carries a much higher rate limit at GitHub. - -This is true for any method of adding GitHub entities to the catalog, but -especially easy to hit with automatic discovery. diff --git a/docs/integrations/github/org--old.md b/docs/integrations/github/org--old.md new file mode 100644 index 0000000000..2f1b39a1f8 --- /dev/null +++ b/docs/integrations/github/org--old.md @@ -0,0 +1,368 @@ +--- +id: org--old +title: GitHub Organizational Data +sidebar_label: Org Data +# prettier-ignore +description: Importing users and groups from a GitHub organization into Backstage +--- + +:::info +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + +The Backstage catalog can be set up to ingest organizational data - users and +teams - directly from an organization in GitHub or GitHub Enterprise. The result +is a hierarchy of +[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and +[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind +entities that mirror your org setup. + +> Note: This adds `User` and `Group` entities to the catalog, but does not +> provide authentication. See the +> [GitHub auth provider](../../auth/github/provider.md) for that. + +## Installation without Events Support + +This guide will use the Entity Provider method. If you for some reason prefer +the Processor method (not recommended), it is described separately below. + +The provider is not installed by default, therefore you have to add a dependency +to `@backstage/plugin-catalog-backend-module-github` to your backend package. + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github +``` + +> Note: When configuring to use a Provider instead of a Processor you do not +> need to add a _location_ pointing to your GitHub server/organization + +Update the catalog plugin initialization in your backend to add the provider and +schedule it: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + + /* highlight-add-start */ + // The org URL below needs to match a configured integrations.github entry + // specified in your app-config. + builder.addEntityProvider( + GithubOrgEntityProvider.fromConfig(env.config, { + id: 'production', + orgUrl: 'https://github.com/backstage', + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +Alternatively, if you wish to ingest data from multiple GitHub organizations you can use +the `GithubMultiOrgEntityProvider` instead. Note that by default, this provider will namespace +groups according to the org they originate from to avoid potential name duplicates: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + + /* highlight-add-start */ + // The GitHub URL below needs to match a configured integrations.github entry + // specified in your app-config. + builder.addEntityProvider( + GithubMultiOrgEntityProvider.fromConfig(env.config, { + id: 'production', + githubUrl: 'https://github.com', + // Set the following to list the GitHub orgs you wish to ingest from. You can + // also omit this option to ingest all orgs accessible by your GitHub integration + orgs: ['org-a', 'org-b'], + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +## Installation with Events Support + +_For the legacy backend system, please read the subsection below._ + +The catalog module `github-org` comes with events support enabled for the `GithubMultiOrgEntityProvider`. +This will make it subscribe to its relevant topics and expects these events to be published via the `EventsService`. + +Topics: + +- `github.installation` +- `github.membership` +- `github.organization` +- `github.team` + +Additionally, you should install the +[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md) +which will route received events from the generic topic `github` to more specific ones +based on the event type (e.g., `github.membership`). + +In order to receive Webhook events by GitHub, you have to decide how you want them +to be ingested into Backstage and published to its `EventsService`. +You can decide between the following options (extensible): + +- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) +- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) + +### Legacy Backend System + +Please follow the installation instructions at + +- +- + +Additionally, you need to decide how you want to receive events from external sources like + +- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) +- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) + +Set up your provider + +```ts title="packages/backend/src/plugins/catalog.ts" +import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +/* highlight-add-next-line */ +import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; +import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + builder.addProcessor(new ScaffolderEntitiesProcessor()); + /* highlight-add-start */ + const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { + id: 'production', + orgUrl: 'https://github.com/backstage', + logger: env.logger, + events: env.events, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }); + builder.addEntityProvider(githubOrgProvider); + /* highlight-add-end */ + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; +} +``` + +Or, alternatively, if using the `GithubMultiOrgEntityProvider`: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + + /* highlight-add-start */ + // The GitHub URL below needs to match a configured integrations.github entry + // specified in your app-config. + builder.addEntityProvider( + GithubMultiOrgEntityProvider.fromConfig(env.config, { + id: 'production', + githubUrl: 'https://github.com', + // Set the following to list the GitHub orgs you wish to ingest from. You can + // also omit this option to ingest all orgs accessible by your GitHub integration + orgs: ['org-a', 'org-b'], + logger: env.logger, + events: env.events, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). +The webhook will need to be configured to forward `organization`,`team` and `membership` events. + +## Configuration + +As mentioned above, you also must have some configuration in your app-config +that describes the targets that you want to import. This lets the entity +provider know what authorization to use, and what the API endpoints are. You may +or may not have such an entry already added since before: + +```yaml +integrations: + github: + # example for public github + - host: github.com + token: ${GITHUB_TOKEN} + # example for a private GitHub Enterprise instance + - host: ghe.example.net + apiBaseUrl: https://ghe.example.net/api/v3 + token: ${GHE_TOKEN} +``` + +These examples use `${}` placeholders to reference environment variables. This +is often suitable for production setups, but also means that you will have to +supply those variables to the backend as it starts up. If you want, for local +development in particular, you can experiment first by putting the actual tokens +in a mirrored config directly in your `app-config.local.yaml` as well. + +If Backstage is configured to use GitHub Apps authentication you must grant +`Read-Only` access for `Members` under `Organization` in order to ingest users +correctly. You can modify the app's permissions under the organization settings, +`https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`. + +![permissions](../../assets/integrations/github/permissions.png) + +**Please note that when you change permissions, the app owner will get an email +that must be approved first before the changes are applied.** + +![email](../../assets/integrations/github/email.png) + +### Custom Transformers + +You can inject your own transformation logic to help map from GH API responses +into backstage entities. You can do this on the user and team requests to +enable you to do further processing or updates to the entities. + +To enable this you pass a function into the `GitHubOrgEntityProvider`. You can +pass a `UserTransformer`, `TeamTransformer` or both. The function is invoked +for each item (user or team) that is returned from the API. You can either +return an Entity (User or Group) or `undefined` if you do not want to import +that item. + +There is also a `defaultUserTransformer` and `defaultOrganizationTeamTransformer`. +You could use these and simply decorate the response from the default +transformation if you only need to change a few properties. + +### Resolving GitHub users via organization email + +When you authenticate users you should resolve them to an entity within the +catalog. Often the authentication you use could be a corporate SSO system that +provides you with email as a key. To enable you to find and resolve GitHub users +it's useful to also import the private domain verified emails into the User +entity in backstage. + +The integration attempts to return `organizationVerifiedDomainEmails` from the +GitHub API and makes this available as part of the object passed to +`UserTransformer`. The GitHub API will only return emails that use a domain +that's a verified domain for your GitHub Org. It also relies on the user having +configured such an email in their own account. The API will only return these +values when using GitHub App authentication and with the correct app permission +allowing access to emails. + +You can decorate the default `userTransformer` to replace the org email in the +returned identity. + +```ts title="packages/backend/src/plugins/catalog.ts" +const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { + id: 'production', + orgUrl: 'https://github.com/backstage', + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + /* highlight-add-start */ + userTransformer: async (user, ctx) => { + const entity = await defaultUserTransformer(user, ctx); + if (entity && user.organizationVerifiedDomainEmails?.length) { + entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0]; + } + return entity; + }, + /* highlight-add-end */ +}); +``` + +Once you have imported the emails you can resolve users in your [sign-in +resolver](../../auth/github/provider.md) using the catalog entity search via email + +```typescript title="packages/backend/src/plugins/auth.ts" +ctx.signInWithCatalogUser({ + filter: { + kind: ['User'], + 'spec.profile.email': email as string, + }, +}); +``` + +## Using a Processor instead of a Provider + +An alternative to using the Provider for ingesting organizational entities is to +use a Processor. This is the old way that's based on registering locations with +the proper type and target, triggering the processor to run. + +The drawback of this method is that it will leave orphaned Group/User entities +whenever they are deleted on your GitHub server, and you cannot control the +frequency with which they are refreshed, separately from other processors. + +### Processor Installation + +The `GithubOrgReaderProcessor` is not registered by default, so you have to +install and register it in the catalog plugin: + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github +``` + +```typescript title="packages/backend/src/plugins/catalog.ts" +import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend-module-github'; + +builder.addProcessor( + GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }), +); +``` + +### Processor Configuration + +The integration section of your app-config needs to be set up in the same way as +for the Entity Provider - see above. + +In addition to that, you typically want to add a few static locations to your +app-config, which reference your organizations to import. The following +configuration enables an import of the teams and users under the org +`https://github.com/my-org-name` on public GitHub. + +```yaml +catalog: + locations: + - type: github-org + target: https://github.com/my-org-name + rules: + - allow: [User, Group] +``` diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 974362c125..a9936ff558 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -6,6 +6,10 @@ sidebar_label: Org Data description: Importing users and groups from a GitHub organization into Backstage --- +:::info +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +::: + The Backstage catalog can be set up to ingest organizational data - users and teams - directly from an organization in GitHub or GitHub Enterprise. The result is a hierarchy of @@ -17,95 +21,28 @@ entities that mirror your org setup. > provide authentication. See the > [GitHub auth provider](../../auth/github/provider.md) for that. -## Installation without Events Support +## Installation -This guide will use the Entity Provider method. If you for some reason prefer -the Processor method (not recommended), it is described separately below. - -The provider is not installed by default, therefore you have to add a dependency -to `@backstage/plugin-catalog-backend-module-github` to your backend package. +You will have to add the GitHub Org provider to your backend as it is not installed by default, therefore you have to add a +dependency on `@backstage/plugin-catalog-backend-module-github-org` to your backend +package. ```bash # From your Backstage root directory -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github-org ``` -> Note: When configuring to use a Provider instead of a Processor you do not -> need to add a _location_ pointing to your GitHub server/organization +And then update your backend by adding the following line: -Update the catalog plugin initialization in your backend to add the provider and -schedule it: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The org URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-github-org')); ``` -Alternatively, if you wish to ingest data from multiple GitHub organizations you can use -the `GithubMultiOrgEntityProvider` instead. Note that by default, this provider will namespace -groups according to the org they originate from to avoid potential name duplicates: +## Events Support -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The GitHub URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubMultiOrgEntityProvider.fromConfig(env.config, { - id: 'production', - githubUrl: 'https://github.com', - // Set the following to list the GitHub orgs you wish to ingest from. You can - // also omit this option to ingest all orgs accessible by your GitHub integration - orgs: ['org-a', 'org-b'], - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Installation with Events Support - -_For the legacy backend system, please read the subsection below._ - -The catalog module `github-org` comes with events support enabled for the `GithubMultiOrgEntityProvider`. +The catalog module for GitHub Org comes with events support enabled. This will make it subscribe to its relevant topics and expects these events to be published via the `EventsService`. Topics: @@ -127,87 +64,6 @@ You can decide between the following options (extensible): - [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) - [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) -### Legacy Backend System - -Please follow the installation instructions at - -- -- - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - events: env.events, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }); - builder.addEntityProvider(githubOrgProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - -Or, alternatively, if using the `GithubMultiOrgEntityProvider`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The GitHub URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubMultiOrgEntityProvider.fromConfig(env.config, { - id: 'production', - githubUrl: 'https://github.com', - // Set the following to list the GitHub orgs you wish to ingest from. You can - // also omit this option to ingest all orgs accessible by your GitHub integration - orgs: ['org-a', 'org-b'], - logger: env.logger, - events: env.events, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `organization`,`team` and `membership` events. @@ -264,6 +120,81 @@ There is also a `defaultUserTransformer` and `defaultOrganizationTeamTransformer You could use these and simply decorate the response from the default transformation if you only need to change a few properties. +Here's an example of how to use the transformers: + +```ts title="packages/backend/src/index.ts" +import { createBackend } from '@backstage/backend-defaults'; +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org'; +import { myTeamTransformer, myUserTransformer } from './transformers'; + +const githubOrgModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'github-org-extensions', + register(env) { + env.registerInit({ + deps: { + githubOrg: githubOrgEntityProviderTransformsExtensionPoint, + }, + async init({ githubOrg }) { + githubOrg.setTeamTransformer(myTeamTransformer); + githubOrg.setUserTransformer(myUserTransformer); + }, + }); + }, +}); + +const backend = createBackend(); + +// Other items + +backend.add(import('@backstage/plugin-catalog-backend/alpha')); + +backend.add(githubOrgModule()); + +backend.start(); +``` + +The `myTeamTransformer` and `myUserTransformer` transformer functions are from the examples in the section below. + +### Transformer Examples + +The following provides an example of each kind of transformer. We recommend creating a `transformers.ts` file in your `packages/backend/src` folder for these. + +```ts title="packages/backend/src/transformers.ts" +import { + TeamTransformer, + UserTransformer, + defaultUserTransformer, +} from '@backstage/plugin-catalog-backend-module-github'; + +// This team transformer completely replaces the built in logic with custom logic. +export const myTeamTransformer: TeamTransformer = async team => { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: team.slug, + annotations: {}, + }, + spec: { + type: 'GitHub Org Team', + profile: {}, + children: [], + }, + }; +}; + +// This user transformer makes use of the built in logic, but also sets the description field +export const myUserTransformer: UserTransformer = async (user, ctx) => { + const backstageUser = await defaultUserTransformer(user, ctx); + if (backstageUser) { + backstageUser.metadata.description = 'Loaded from GitHub Org Data'; + } + return backstageUser; +}; +``` + ### Resolving GitHub users via organization email When you authenticate users you should resolve them to an entity within the @@ -283,31 +214,25 @@ allowing access to emails. You can decorate the default `userTransformer` to replace the org email in the returned identity. -```ts title="packages/backend/src/plugins/catalog.ts" -const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - /* highlight-add-start */ - userTransformer: async (user, ctx) => { - const entity = await defaultUserTransformer(user, ctx); - if (entity && user.organizationVerifiedDomainEmails?.length) { - entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0]; - } - return entity; - }, - /* highlight-add-end */ -}); +```ts title="packages/backend/src/transformers.ts" +export const myVerifiedUserTransformer: UserTransformer = async (user, ctx) => { + const backstageUser = await defaultUserTransformer(user, ctx); + if (backstageUser && user.organizationVerifiedDomainEmails?.length) { + backstageUser.spec.profile!.email = + user.organizationVerifiedDomainEmails[0]; + } + return backstageUser; +}; ``` +This example assumes you have implemented the custom transformer following the [Custom Transformers](#custom-transformers) and [Transformer Examples](#transformer-examples) documentation in the sections above. + Once you have imported the emails you can resolve users in your [sign-in resolver](../../auth/github/provider.md) using the catalog entity search via email -```typescript title="packages/backend/src/plugins/auth.ts" +Once you have imported the emails you can resolve users by building a [Custom Resolver](../../auth/identity-resolver.md#building-custom-resolvers). In this custom resolver you can then use this example to properly match the user: + +```ts ctx.signInWithCatalogUser({ filter: { kind: ['User'], @@ -315,50 +240,3 @@ ctx.signInWithCatalogUser({ }, }); ``` - -## Using a Processor instead of a Provider - -An alternative to using the Provider for ingesting organizational entities is to -use a Processor. This is the old way that's based on registering locations with -the proper type and target, triggering the processor to run. - -The drawback of this method is that it will leave orphaned Group/User entities -whenever they are deleted on your GitHub server, and you cannot control the -frequency with which they are refreshed, separately from other processors. - -### Processor Installation - -The `GithubOrgReaderProcessor` is not registered by default, so you have to -install and register it in the catalog plugin: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github -``` - -```typescript title="packages/backend/src/plugins/catalog.ts" -import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend-module-github'; - -builder.addProcessor( - GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }), -); -``` - -### Processor Configuration - -The integration section of your app-config needs to be set up in the same way as -for the Entity Provider - see above. - -In addition to that, you typically want to add a few static locations to your -app-config, which reference your organizations to import. The following -configuration enables an import of the teams and users under the org -`https://github.com/my-org-name` on public GitHub. - -```yaml -catalog: - locations: - - type: github-org - target: https://github.com/my-org-name - rules: - - allow: [User, Group] -``` From 2a6f10d77a4d7b86f43a5d6915959093c6572970 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 2 May 2024 14:31:05 +0200 Subject: [PATCH 169/567] cli: only warn when bump fails + fix forbidden duplicate filter Signed-off-by: Patrik Oldsberg --- .changeset/kind-toes-scream.md | 5 ++ .../cli/src/commands/versions/bump.test.ts | 65 +++++++------------ packages/cli/src/commands/versions/bump.ts | 26 ++++---- packages/cli/src/commands/versions/lint.ts | 5 +- 4 files changed, 41 insertions(+), 60 deletions(-) create mode 100644 .changeset/kind-toes-scream.md diff --git a/.changeset/kind-toes-scream.md b/.changeset/kind-toes-scream.md new file mode 100644 index 0000000000..d634f17ce4 --- /dev/null +++ b/.changeset/kind-toes-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `versions:bump` command will no longer exit with a non-zero status if the version bump fails due to forbidden duplicate package installations. It will now also provide more information about how to troubleshoot such an error. The set of forbidden duplicates has also been expanded to include all `@backstage/*-app-api` packages. diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index c8174639a6..0dfe2f61c3 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -894,30 +894,19 @@ describe('bump', () => { newVersions: [], newRanges: [ { - name: 'first-duplicate', - oldRange: 'first-duplicate', - newRange: 'first-duplicate', - oldVersion: '1.0.0', - newVersion: '2.0.0', - }, - { - name: 'second-duplicate', - oldRange: 'second-duplicate', - newRange: 'second-duplicate', - oldVersion: '1.0.0', - newVersion: '2.0.0', - }, - { - name: 'third-duplicate', - oldRange: 'third-duplicate', - newRange: 'third-duplicate', + 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': lockfileMock, + 'yarn.lock': `${HEADER} +"@backstage/backend-app-api@^1.0.0": + version "1.0.0" +`, 'package.json': JSON.stringify({ workspaces: { packages: ['packages/*'], @@ -928,16 +917,7 @@ describe('bump', () => { 'package.json': JSON.stringify({ name: 'a', dependencies: { - '@backstage/core': '^1.0.5', - }, - }), - }, - b: { - 'package.json': JSON.stringify({ - name: 'b', - dependencies: { - '@backstage/core': '^1.0.3', - '@backstage/theme': '^1.0.0', + '@backstage/backend-app-api': '^1.0.0', }, }), }, @@ -952,7 +932,12 @@ describe('bump', () => { res( ctx.status(200), ctx.json({ - packages: [], + packages: [ + { + name: '@backstage/backend-app-api', + version: '2.0.0', + }, + ], }), ), ), @@ -962,24 +947,20 @@ describe('bump', () => { }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', - 'Checking for updates of @backstage/core', - 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', + 'Checking for updates of @backstage/backend-app-api', + 'Checking for updates of @backstage/backend-app-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', + '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/theme : 1.0.0 ~> 2.0.0', - ' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md', + ' @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!', - 'The following packages have duplicates but have been allowed:', - 'first-duplicate, second-duplicate, third-duplicate', + ' ⚠️ 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', ]); }); }); diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index aad5a7831f..1544240704 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -331,24 +331,22 @@ export default async (opts: OptionValues) => { forbiddenDuplicatesFilter(name), ); if (forbiddenNewRanges.length > 0) { - throw new Error( - `Version bump failed for ${forbiddenNewRanges - .map(i => i.name) - .join(', ')}`, - ); - } - - const allowedDuplicates = result.newRanges.filter( - ({ name }) => !forbiddenDuplicatesFilter(name), - ); - - if (allowedDuplicates.length > 0) { + console.log(chalk.yellow(' ⚠️ Warning! ⚠️')); + console.log(); console.log( chalk.yellow( - 'The following packages have duplicates but have been allowed:', + ' The below package(s) have incompatible duplicate installations, likely due to a bad dependency in a plugin.', ), ); - console.log(chalk.yellow(allowedDuplicates.map(i => i.name).join(', '))); + 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}`)); + } } }; diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index 9466fd0ab7..bfb2a752a0 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -27,10 +27,7 @@ export const includedFilter = (name: string) => INCLUDED.some(pattern => pattern.test(name)); // Packages that are not allowed to have any duplicates -const FORBID_DUPLICATES = [ - /^@backstage\/core-app-api$/, - /^@backstage\/plugin-/, -]; +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 From e538b100433070d3dd6f06c3a7a115047ac3c02b Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 30 Apr 2024 09:50:26 +0300 Subject: [PATCH 170/567] feat: support relative notification links sent via email processor Signed-off-by: Heikki Hellgren --- .changeset/giant-donkeys-talk.md | 5 + .../NotificationsEmailProcessor.test.ts | 225 ++++++++++++------ .../processor/NotificationsEmailProcessor.ts | 38 ++- 3 files changed, 188 insertions(+), 80 deletions(-) create mode 100644 .changeset/giant-donkeys-talk.md diff --git a/.changeset/giant-donkeys-talk.md b/.changeset/giant-donkeys-talk.md new file mode 100644 index 0000000000..62af2eb7e8 --- /dev/null +++ b/.changeset/giant-donkeys-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +--- + +Support relative links in notifications sent via email diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 11eef9b999..cdc7b9059a 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -30,6 +30,36 @@ jest.mock('nodemailer', () => ({ createTransport: jest.fn(), })); +const DEFAULT_ENTITIES_RESPONSE = { + items: [ + { + kind: 'User', + spec: { + profile: { + email: 'mock@backstage.io', + }, + }, + }, + ], +}; + +const DEFAULT_SENDMAIL_CONFIG = { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + email: { + transportConfig: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + }, + }, + }, +}; + describe('NotificationsEmailProcessor', () => { const logger = mockServices.logger.mock(); const auth = mockServices.auth(); @@ -49,6 +79,10 @@ describe('NotificationsEmailProcessor', () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ + app: { + baseUrl: 'http://localhost:3000', + externalBaseUrl: 'https://example.org', + }, notifications: { processors: { email: { @@ -95,6 +129,10 @@ describe('NotificationsEmailProcessor', () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ + app: { + baseUrl: 'http://localhost:3000', + externalBaseUrl: 'https://example.org', + }, notifications: { processors: { email: { @@ -135,6 +173,10 @@ describe('NotificationsEmailProcessor', () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ + app: { + baseUrl: 'http://localhost:3000', + externalBaseUrl: 'https://example.org', + }, notifications: { processors: { email: { @@ -175,29 +217,10 @@ describe('NotificationsEmailProcessor', () => { it('should send user email', async () => { (createTransport as jest.Mock).mockReturnValue(mockTransport); - getEntityRefMock.mockResolvedValue({ - kind: 'User', - spec: { - profile: { - email: 'mock@backstage.io', - }, - }, - }); + getEntityRefMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE.items[0]); const processor = new NotificationsEmailProcessor( logger, - new ConfigReader({ - notifications: { - processors: { - email: { - transportConfig: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - }, - }, - }, - }), + mockServices.rootConfig({ data: DEFAULT_SENDMAIL_CONFIG }), mockCatalogClient as unknown as CatalogClient, auth, ); @@ -218,41 +241,29 @@ describe('NotificationsEmailProcessor', () => { expect(sendmailMock).toHaveBeenCalledWith({ from: 'backstage@backstage.io', - html: '

', + html: '

https://example.org/notifications

', replyTo: undefined, subject: 'notification', - text: '', + text: 'https://example.org/notifications', to: 'mock@backstage.io', }); }); it('should send email to all', async () => { (createTransport as jest.Mock).mockReturnValue(mockTransport); - getEntitiesMock.mockResolvedValue({ - items: [ - { - kind: 'User', - spec: { - profile: { - email: 'mock@backstage.io', - }, - }, - }, - ], - }); + getEntitiesMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE); const processor = new NotificationsEmailProcessor( logger, - new ConfigReader({ - notifications: { - processors: { - email: { - transportConfig: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - broadcastConfig: { - receiver: 'users', + mockServices.rootConfig({ + data: { + ...DEFAULT_SENDMAIL_CONFIG, + notifications: { + processors: { + email: { + ...DEFAULT_SENDMAIL_CONFIG.notifications.processors.email, + broadcastConfig: { + receiver: 'users', + }, }, }, }, @@ -278,42 +289,30 @@ describe('NotificationsEmailProcessor', () => { expect(sendmailMock).toHaveBeenCalledWith({ from: 'backstage@backstage.io', - html: '

', + html: '

https://example.org/notifications

', replyTo: undefined, subject: 'notification', - text: '', + text: 'https://example.org/notifications', to: 'mock@backstage.io', }); }); it('should send email to configured addresses', async () => { (createTransport as jest.Mock).mockReturnValue(mockTransport); - getEntitiesMock.mockResolvedValue({ - items: [ - { - kind: 'User', - spec: { - profile: { - email: 'mock@backstage.io', - }, - }, - }, - ], - }); + getEntitiesMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE); const processor = new NotificationsEmailProcessor( logger, - new ConfigReader({ - notifications: { - processors: { - email: { - transportConfig: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - broadcastConfig: { - receiver: 'config', - receiverEmails: ['broadcast@backstage.io'] as JsonArray, + mockServices.rootConfig({ + data: { + ...DEFAULT_SENDMAIL_CONFIG, + notifications: { + processors: { + email: { + ...DEFAULT_SENDMAIL_CONFIG.notifications.processors.email, + broadcastConfig: { + receiver: 'config', + receiverEmails: ['broadcast@backstage.io'] as JsonArray, + }, }, }, }, @@ -339,11 +338,89 @@ describe('NotificationsEmailProcessor', () => { expect(sendmailMock).toHaveBeenCalledWith({ from: 'backstage@backstage.io', - html: '

', + html: '

https://example.org/notifications

', replyTo: undefined, subject: 'notification', - text: '', + text: 'https://example.org/notifications', to: 'broadcast@backstage.io', }); }); + + it('should send email with relative link to given address', async () => { + (createTransport as jest.Mock).mockReturnValue(mockTransport); + getEntityRefMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE.items[0]); + const processor = new NotificationsEmailProcessor( + logger, + mockServices.rootConfig({ + data: DEFAULT_SENDMAIL_CONFIG, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + link: 'catalog/user/default/john.doe', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '

https://example.org/catalog/user/default/john.doe

', + replyTo: undefined, + subject: 'notification', + text: 'https://example.org/catalog/user/default/john.doe', + to: 'mock@backstage.io', + }); + }); + + it('should send email with absolute link to given address', async () => { + (createTransport as jest.Mock).mockReturnValue(mockTransport); + getEntityRefMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE.items[0]); + const processor = new NotificationsEmailProcessor( + logger, + mockServices.rootConfig({ + data: DEFAULT_SENDMAIL_CONFIG, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + link: 'https://backstage.io', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '

https://backstage.io/

', + replyTo: undefined, + subject: 'notification', + text: 'https://backstage.io/', + to: 'mock@backstage.io', + }); + }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index ce944d9592..ecf4eb7773 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -50,6 +50,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly cacheTtl: number; private readonly concurrencyLimit: number; private readonly throttleInterval: number; + private readonly frontendBaseUrl: string; constructor( private readonly logger: LoggerService, @@ -78,6 +79,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { this.cacheTtl = cacheConfig ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) : 3_600_000; + this.frontendBaseUrl = config.getString('app.baseUrl'); } private async getTransporter() { @@ -215,20 +217,44 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ); } - private async sendPlainEmail(notification: Notification, emails: string[]) { + private getNotificationLink(notification: Notification) { + if (notification.payload.link) { + try { + const url = new URL(notification.payload.link, this.frontendBaseUrl); + return url.toString(); + } catch (_e) { + // noop: fallback to relative URL + } + return notification.payload.link; + } + return `${this.frontendBaseUrl}/notifications`; + } + + private getHtmlContent(notification: Notification) { const contentParts: string[] = []; if (notification.payload.description) { contentParts.push(`${notification.payload.description}`); } - if (notification.payload.link) { - contentParts.push(`${notification.payload.link}`); - } + const link = this.getNotificationLink(notification); + contentParts.push(`${link}`); + return `

${contentParts.join('
')}

`; + } + private getTextContent(notification: Notification) { + const contentParts: string[] = []; + if (notification.payload.description) { + contentParts.push(notification.payload.description); + } + contentParts.push(this.getNotificationLink(notification)); + return contentParts.join('\n\n'); + } + + private async sendPlainEmail(notification: Notification, emails: string[]) { const mailOptions = { from: this.sender, subject: notification.payload.title, - html: `

${contentParts.join('
')}

`, - text: contentParts.join('\n\n'), + html: this.getHtmlContent(notification), + text: this.getTextContent(notification), replyTo: this.replyTo, }; From 79bb100f0526888566b93b270ffb1b0ac0c9cced Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 30 Apr 2024 13:24:20 +0300 Subject: [PATCH 171/567] fix: ensure proper slashes in the email url Signed-off-by: Heikki Hellgren --- .../NotificationsEmailProcessor.test.ts | 26 +++++++++++++++++++ .../processor/NotificationsEmailProcessor.ts | 8 +++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index cdc7b9059a..79d1110968 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -383,6 +383,32 @@ describe('NotificationsEmailProcessor', () => { text: 'https://example.org/catalog/user/default/john.doe', to: 'mock@backstage.io', }); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + link: '/catalog/user/default/jane.doe', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '

https://example.org/catalog/user/default/jane.doe

', + replyTo: undefined, + subject: 'notification', + text: 'https://example.org/catalog/user/default/jane.doe', + to: 'mock@backstage.io', + }); }); it('should send email with absolute link to given address', async () => { diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index ecf4eb7773..c06d28f903 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -219,8 +219,14 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private getNotificationLink(notification: Notification) { if (notification.payload.link) { + const stripLeadingSlash = (s: string) => s.replace(/^\//, ''); + const ensureTrailingSlash = (s: string) => s.replace(/\/?$/, '/'); + try { - const url = new URL(notification.payload.link, this.frontendBaseUrl); + const url = new URL( + stripLeadingSlash(notification.payload.link), + ensureTrailingSlash(this.frontendBaseUrl), + ); return url.toString(); } catch (_e) { // noop: fallback to relative URL From 8e9727b825d401bbfbafd22785335af20a41bad7 Mon Sep 17 00:00:00 2001 From: Chap Ambrose Date: Thu, 2 May 2024 08:22:18 -0500 Subject: [PATCH 172/567] add ensureSchemaExists to config schema Signed-off-by: Chap Ambrose --- packages/backend-common/config.d.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 700d9a6c6c..bdbec3a352 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -111,6 +111,13 @@ export interface Config { * Defaults to true if unspecified. */ ensureExists?: boolean; + /** + * Whether to ensure the given database schema exists by creating it if it does not. + * Defaults to false if unspecified. + * + * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + */ + ensureSchemaExists?: boolean; /** * How plugins databases are managed/divided in the provided database instance. * @@ -147,6 +154,13 @@ export interface Config { * Defaults to base config if unspecified. */ ensureExists?: boolean; + /** + * Whether to ensure the given database schema exists by creating it if it does not. + * Defaults to false if unspecified. + * + * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + */ + ensureSchemaExists?: boolean; /** * Arbitrary config object to pass to knex when initializing * (https://knexjs.org/#Installation-client). Most notable is the From 2e14b0e9242ea5ae09594a686bdf40b844858c93 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Thu, 2 May 2024 08:46:27 -0500 Subject: [PATCH 173/567] Changes based on feedback Signed-off-by: Andre Wanlin --- docs/integrations/github/github-apps.md | 43 +++++++++-- docs/integrations/github/org.md | 99 ++++++++++++++----------- 2 files changed, 93 insertions(+), 49 deletions(-) diff --git a/docs/integrations/github/github-apps.md b/docs/integrations/github/github-apps.md index 604713a490..6e3d028e6d 100644 --- a/docs/integrations/github/github-apps.md +++ b/docs/integrations/github/github-apps.md @@ -31,7 +31,7 @@ A GitHub app created with the cli will have read access by default. You have to manually update the GitHub App settings in GitHub to grant the app more permissions if needed. -### Using the CLI (public GitHub only) +## Using the CLI (public GitHub only) You can use the `backstage-cli` to create a GitHub App using a manifest file that we provide. This gives us a way to automate some of the work required to @@ -53,7 +53,7 @@ Note that the created app will have a webhook that is disabled by default and points to `smee.io`, which is intended for local development. There's also currently no part of Backstage that makes use of the webhook. -### GitHub Enterprise +## GitHub Enterprise You have to create the GitHub Application manually using these [instructions](https://docs.github.com/en/free-pro-team@latest/developers/apps/creating-a-github-app) @@ -76,7 +76,7 @@ privateKey: | -----END RSA PRIVATE KEY----- ``` -### Including in Integrations Config +## Including in Integrations Config Once the credentials are stored in a YAML file generated by `create-github-app`, or manually by following the [GitHub Enterprise](#github-enterprise) @@ -95,7 +95,25 @@ integrations: - $include: example-backstage-app-credentials.yaml ``` -### Limiting the GitHub App installations +Alternatively you can use environment variables as well: + +```yaml +integrations: + github: + - host: github.com + apps: + - appId: ${AUTH_ORG_APP_ID} + clientId: ${AUTH_ORG_CLIENT_ID} + clientSecret: ${AUTH_ORG_CLIENT_SECRET} + privateKey: ${AUTH_ORG1_PRIVATE_KEY} + webhookSecret: ${AUTH_ORG_WEBHOOK_SECRET} +``` + +:::Note +Note that in both examples above `apps` is an array which means you can add multiple GitHub Apps using `$include` or environment variables as long as they are each for a different GitHub Org as mentioned under the [Caveats](#caveats) section +::: + +## Limiting the GitHub App installations If you want to limit the GitHub app installations visible to backstage you may optionally include the `allowedInstallationOwners` option. If you configure @@ -117,7 +135,7 @@ privateKey: | This will result in backstage preventing the use of any installation that is not within the allow list. -### App permissions +## App permissions When creating a GitHub App, you must select permissions to define the level of access for the app. The permissions required vary depending on your use of the @@ -140,7 +158,20 @@ integration: - `Secrets`: `Read & write` (if templates include GitHub Action Repository Secrets) - `Environments`: `Read & write` (if templates include GitHub Environments) -### Troubleshooting +## Updating Permissions + +There may be times where you need to update the permissions for your GitHub App, to easily get at the GitHub App you can find it at this URL: + +```sh +https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions +``` + +**Please note that when you change permissions, the app owner will get an email +that must be approved first before the changes are applied.** + +![email](../../assets/integrations/github/email.png) + +## Troubleshooting `HttpError: This endpoint requires you to be authenticated.` diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index a9936ff558..0b33cfa184 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -21,6 +21,13 @@ entities that mirror your org setup. > provide authentication. See the > [GitHub auth provider](../../auth/github/provider.md) for that. +## Permissions + +Prior to installing the GitHub Org provider you should confirm you have the right permissions: + +- Personal Access Token permissions are listed in the [GitHub Locations](./locations.md#token-scopes) documentation +- GitHub App(s) permissions are listed in the [GitHub Apps](./github-apps.md#app-permissions) documentation + ## Installation You will have to add the GitHub Org provider to your backend as it is not installed by default, therefore you have to add a @@ -32,7 +39,22 @@ package. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github-org ``` -And then update your backend by adding the following line: +Next add the basic configuration to `app-config.yaml` + +```yaml title="app-config.yaml" +catalog: + providers: + githubOrg: + id: github + githubUrl: https://github.com + orgs: ['organization-1', 'organization-2', 'organization-3'] + schedule: + initialDelay: { seconds: 30 } + frequency: { hours: 1 } + timeout: { minutes: 50 } +``` + +Finally, update your backend by adding the following line: ```ts title="packages/backend/src/index.ts" backend.add(import('@backstage/plugin-catalog-backend/alpha')); @@ -40,7 +62,38 @@ backend.add(import('@backstage/plugin-catalog-backend/alpha')); backend.add(import('@backstage/plugin-catalog-backend-module-github-org')); ``` -## Events Support +### Configuration Details + +In the installation steps above we included an simple example of the needed configuration. The section goes into more details about the various configuration options. + +```yaml title="app-config.yaml" +catalog: + providers: + githubOrg: + - id: github + githubUrl: https://github.com + orgs: ['organization-1', 'organization-2', 'organization-3'] + schedule: + initialDelay: { seconds: 30 } + frequency: { hours: 1 } + timeout: { minutes: 50 } + - id: ghe + githubUrl: https://ghe.mycompany.com + orgs: ['internal-1', 'internal-2', 'internal-3'] + schedule: + initialDelay: { seconds: 30 } + frequency: { hours: 1 } + timeout: { minutes: 50 } +``` + +Directly under the `githubOrg` is a list of configurations, each entry is a structure with the following elements: + +- `id`: A stable id for this provider. Entities from this provider will be associated with this ID, so you should take care not to change it over time since that may lead to orphaned entities and/or conflicts. +- `githubUrl`: The target that this provider should consume +- `orgs` (optional): The list of the GitHub orgs to consume. By default wil consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). +- `schedule`: The refresh schedule to use, matches the structure of [`TaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinitionconfig/) + +### Events Support The catalog module for GitHub Org comes with events support enabled. This will make it subscribe to its relevant topics and expects these events to be published via the `EventsService`. @@ -67,44 +120,7 @@ You can decide between the following options (extensible): You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `organization`,`team` and `membership` events. -## Configuration - -As mentioned above, you also must have some configuration in your app-config -that describes the targets that you want to import. This lets the entity -provider know what authorization to use, and what the API endpoints are. You may -or may not have such an entry already added since before: - -```yaml -integrations: - github: - # example for public github - - host: github.com - token: ${GITHUB_TOKEN} - # example for a private GitHub Enterprise instance - - host: ghe.example.net - apiBaseUrl: https://ghe.example.net/api/v3 - token: ${GHE_TOKEN} -``` - -These examples use `${}` placeholders to reference environment variables. This -is often suitable for production setups, but also means that you will have to -supply those variables to the backend as it starts up. If you want, for local -development in particular, you can experiment first by putting the actual tokens -in a mirrored config directly in your `app-config.local.yaml` as well. - -If Backstage is configured to use GitHub Apps authentication you must grant -`Read-Only` access for `Members` under `Organization` in order to ingest users -correctly. You can modify the app's permissions under the organization settings, -`https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`. - -![permissions](../../assets/integrations/github/permissions.png) - -**Please note that when you change permissions, the app owner will get an email -that must be approved first before the changes are applied.** - -![email](../../assets/integrations/github/email.png) - -### Custom Transformers +## Custom Transformers You can inject your own transformation logic to help map from GH API responses into backstage entities. You can do this on the user and team requests to @@ -227,9 +243,6 @@ export const myVerifiedUserTransformer: UserTransformer = async (user, ctx) => { This example assumes you have implemented the custom transformer following the [Custom Transformers](#custom-transformers) and [Transformer Examples](#transformer-examples) documentation in the sections above. -Once you have imported the emails you can resolve users in your [sign-in -resolver](../../auth/github/provider.md) using the catalog entity search via email - Once you have imported the emails you can resolve users by building a [Custom Resolver](../../auth/identity-resolver.md#building-custom-resolvers). In this custom resolver you can then use this example to properly match the user: ```ts From 8834dafd474812f920c33462222430991e628c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 2 May 2024 15:59:06 +0200 Subject: [PATCH 174/567] Added for easier consumption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/proud-comics-love.md | 6 ++ plugins/catalog-react/api-report.md | 1 + .../EntityPresentationApi.ts | 4 + .../useEntityPresentation.ts | 3 +- .../EntityDisplayName.test.tsx | 4 + .../DefaultEntityPresentationApi.test.ts | 72 +++++++++------ .../DefaultEntityPresentationApi.ts | 91 ++++++++++++------- 7 files changed, 117 insertions(+), 64 deletions(-) create mode 100644 .changeset/proud-comics-love.md diff --git a/.changeset/proud-comics-love.md b/.changeset/proud-comics-love.md new file mode 100644 index 0000000000..6891bf937b --- /dev/null +++ b/.changeset/proud-comics-love.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog': minor +--- + +Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 55064ff91f..79aad53c33 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -460,6 +460,7 @@ export type EntityRefLinksProps< // @public export interface EntityRefPresentation { + promise: Promise; snapshot: EntityRefPresentationSnapshot; update$?: Observable; } diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts index 945bce1d27..642dd6dbac 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts @@ -109,6 +109,10 @@ export interface EntityRefPresentation { * elsewhere. */ update$?: Observable; + /** + * A promise that resolves to a usable entity presentation. + */ + promise: Promise; } /** diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts index bae3936cc8..e397558ed0 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts @@ -61,7 +61,8 @@ export function useEntityPresentation( const presentation = useMemo( () => { if (!entityPresentationApi) { - return { snapshot: defaultEntityPresentation(entityOrRef, context) }; + const fallback = defaultEntityPresentation(entityOrRef, context); + return { snapshot: fallback, promise: Promise.resolve(fallback) }; } return entityPresentationApi.forEntity( diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx index 573cfa9011..3a3816e1e3 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.test.tsx @@ -73,6 +73,10 @@ describe('', () => { update$: new ObservableImpl(subscriber => { promise.then(value => subscriber.next(value)); }), + promise: Promise.resolve({ + entityRef: 'component:default/foo', + primaryTitle: 'foo', + }), } as EntityRefPresentation); await renderInTestApp( diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts index 42ff777cbd..4cc4d79f52 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts @@ -23,10 +23,11 @@ import { import { DefaultEntityPresentationApi } from './DefaultEntityPresentationApi'; describe('DefaultEntityPresentationApi', () => { - it('works in local mode', () => { + it('works in local mode', async () => { const api = DefaultEntityPresentationApi.createLocal(); - expect(api.forEntity('component:default/test')).toEqual({ + let presentation = api.forEntity('component:default/test'); + expect(presentation).toEqual({ snapshot: { entityRef: 'component:default/test', entity: undefined, @@ -35,11 +36,14 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: expect.any(Promise), }); + await expect(presentation.promise).resolves.toEqual(presentation.snapshot); - expect( - api.forEntity('component:default/test', { defaultKind: 'Other' }), - ).toEqual({ + presentation = api.forEntity('component:default/test', { + defaultKind: 'Other', + }); + expect(presentation).toEqual({ snapshot: { entityRef: 'component:default/test', entity: undefined, @@ -48,13 +52,14 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: expect.any(Promise), }); + await expect(presentation.promise).resolves.toEqual(presentation.snapshot); - expect( - api.forEntity('component:default/test', { - defaultNamespace: 'other', - }), - ).toEqual({ + presentation = api.forEntity('component:default/test', { + defaultNamespace: 'other', + }); + expect(presentation).toEqual({ snapshot: { entityRef: 'component:default/test', entity: undefined, @@ -63,7 +68,9 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: expect.any(Promise), }); + await expect(presentation.promise).resolves.toEqual(presentation.snapshot); const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', @@ -77,7 +84,8 @@ describe('DefaultEntityPresentationApi', () => { }, }; - expect(api.forEntity(entity)).toEqual({ + presentation = api.forEntity(entity); + expect(presentation).toEqual({ snapshot: { entityRef: 'component:default/test', primaryTitle: 'test', @@ -85,7 +93,9 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, + promise: expect.any(Promise), }); + await expect(presentation.promise).resolves.toEqual(presentation.snapshot); }); it('works in catalog mode', async () => { @@ -114,34 +124,38 @@ describe('DefaultEntityPresentationApi', () => { }); // return simple presentation, call catalog, return full presentation - await expect( - consumePresentation(api.forEntity('component:default/test')), - ).resolves.toEqual([ + let presentation = api.forEntity('component:default/test'); + let expected: EntityRefPresentationSnapshot = { + entityRef: 'component:default/test', + primaryTitle: 'test', + secondaryTitle: 'component:default/test | service', + Icon: expect.anything(), + }; + await expect(consumePresentation(presentation)).resolves.toEqual([ + // first the dummy snapshot { entityRef: 'component:default/test', primaryTitle: 'test', secondaryTitle: 'component:default/test', Icon: expect.anything(), }, - { - entityRef: 'component:default/test', - primaryTitle: 'test', - secondaryTitle: 'component:default/test | service', - Icon: expect.anything(), - }, + expected, ]); + await expect(presentation.promise).resolves.toEqual(expected); // use cached entity, immediately return full presentation - await expect( - consumePresentation(api.forEntity('component:default/test')), - ).resolves.toEqual([ - { - entityRef: 'component:default/test', - primaryTitle: 'test', - secondaryTitle: 'component:default/test | service', - Icon: expect.anything(), - }, + presentation = api.forEntity('component:default/test'); + expected = { + entityRef: 'component:default/test', + primaryTitle: 'test', + secondaryTitle: 'component:default/test | service', + Icon: expect.anything(), + }; + expect(presentation.snapshot).toEqual(expected); + await expect(consumePresentation(presentation)).resolves.toEqual([ + expected, ]); + await expect(presentation.promise).resolves.toEqual(presentation.snapshot); expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledTimes(1); expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledWith( diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts index 82862be435..9630c6c216 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts @@ -260,47 +260,70 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { }; } + if (!needsLoad) { + return { + snapshot: initialSnapshot, + promise: Promise.resolve(initialSnapshot), + }; + } + + const loadingPromise = Promise.resolve().then(() => + this.#loader?.load(entityRef), + ); + // And then the following snapshot - const observable = !needsLoad - ? undefined - : new ObservableImpl(subscriber => { - let aborted = false; + const observable = new ObservableImpl( + subscriber => { + let aborted = false; - Promise.resolve() - .then(() => this.#loader?.load(entityRef)) - .then(newEntity => { - if ( - !aborted && - newEntity && - newEntity.metadata.etag !== entity?.metadata.etag - ) { - const updatedSnapshot = render({ - loading: false, - entity: newEntity, - }); - subscriber.next(updatedSnapshot); - } - }) - .catch(() => { - // Intentionally ignored - we do not propagate errors to the - // observable here. The presentation API should be error free and - // always return SOMETHING that makes sense to render, and we have - // already ensured above that the initial snapshot was that. - }) - .finally(() => { - if (!aborted) { - subscriber.complete(); - } - }); + loadingPromise + .then(newEntity => { + if ( + !aborted && + newEntity && + newEntity.metadata.etag !== entity?.metadata.etag + ) { + const updatedSnapshot = render({ + loading: false, + entity: newEntity, + }); + subscriber.next(updatedSnapshot); + } + }) + .catch(() => { + // Intentionally ignored - we do not propagate errors to the + // observable here. The presentation API should be error free and + // always return SOMETHING that makes sense to render, and we have + // already ensured above that the initial snapshot was that. + }) + .finally(() => { + if (!aborted) { + subscriber.complete(); + } + }); - return () => { - aborted = true; - }; - }); + return () => { + aborted = true; + }; + }, + ); + + const promise = loadingPromise + .then(newEntity => { + if (newEntity && newEntity.metadata.etag !== entity?.metadata.etag) { + return render({ + loading: false, + entity: newEntity, + }); + } + return initialSnapshot; + }) + .catch(() => initialSnapshot); return { snapshot: initialSnapshot, update$: observable, + promise: promise, }; } From 99fe60b13e017bdb73a073b554e1891a3e76d12c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 2 May 2024 16:01:08 +0200 Subject: [PATCH 175/567] Apply suggestions from code review Signed-off-by: Patrik Oldsberg --- packages/backend-common/config.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index bdbec3a352..c6263cde97 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -115,7 +115,7 @@ export interface Config { * Whether to ensure the given database schema exists by creating it if it does not. * Defaults to false if unspecified. * - * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema */ ensureSchemaExists?: boolean; /** @@ -158,7 +158,7 @@ export interface Config { * Whether to ensure the given database schema exists by creating it if it does not. * Defaults to false if unspecified. * - * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema */ ensureSchemaExists?: boolean; /** From 83643ef0919f5dadcdb4ea7ea62bcf593dc04698 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 10:16:25 -0400 Subject: [PATCH 176/567] chore: add util to perform basic permission check Signed-off-by: Frank Kong --- .../scaffolder-backend/src/service/router.ts | 147 +++++------------- .../src/util/checkPermissions.ts | 53 +++++++ 2 files changed, 95 insertions(+), 105 deletions(-) create mode 100644 plugins/scaffolder-backend/src/util/checkPermissions.ts diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 228f7bed12..3c98adc3f4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -103,6 +103,7 @@ import { IdentityApiGetIdentityRequest, } from '@backstage/plugin-auth-node'; import { InternalTaskSecrets } from '../scaffolder/tasks/types'; +import { checkPermission } from '../util/checkPermissions'; /** * @@ -492,19 +493,11 @@ export async function createRouter( ) .get('/v2/actions', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: actionReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [actionReadPermission], + permissionService: permissions, + }); const actionsList = actionRegistry.list().map(action => { return { id: action.id, @@ -522,18 +515,11 @@ export async function createRouter( }); const credentials = await httpAuth.credentials(req); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskCreatePermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskCreatePermission], + permissionService: permissions, + }); const { token } = await auth.getPluginRequestToken({ onBehalfOf: credentials, @@ -612,22 +598,13 @@ export async function createRouter( }) .get('/v2/tasks', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); const [userEntityRef] = [req.query.createdBy].flat(); - if ( typeof userEntityRef !== 'string' && typeof userEntityRef !== 'undefined' @@ -649,19 +626,11 @@ export async function createRouter( }) .get('/v2/tasks/:taskId', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); const { taskId } = req.params; const task = await taskBroker.get(taskId); @@ -674,22 +643,12 @@ export async function createRouter( }) .post('/v2/tasks/:taskId/cancel', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponses = await permissions.authorizeConditional( - [ - { permission: taskCancelPermission }, - { permission: taskReadPermission }, - ], - { credentials: credentials }, - ); - // Requires both read and cancel permissions - for (const response of authorizationResponses) { - if (response.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } - } + // Requires both read and cancel permissions + await checkPermission({ + credentials, + permissions: [taskCancelPermission, taskReadPermission], + permissionService: permissions, + }); const { taskId } = req.params; await taskBroker.cancel?.(taskId); @@ -697,19 +656,12 @@ export async function createRouter( }) .get('/v2/tasks/:taskId/eventstream', async (req, res) => { const credentials = await httpAuth.credentials(req); + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } const { taskId } = req.params; const after = req.query.after !== undefined ? Number(req.query.after) : undefined; @@ -759,19 +711,12 @@ export async function createRouter( }) .get('/v2/tasks/:taskId/events', async (req, res) => { const credentials = await httpAuth.credentials(req); + await checkPermission({ + credentials, + permissions: [taskReadPermission], + permissionService: permissions, + }); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskReadPermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } const { taskId } = req.params; const after = Number(req.query.after) || undefined; @@ -803,19 +748,11 @@ export async function createRouter( }) .post('/v2/dry-run', async (req, res) => { const credentials = await httpAuth.credentials(req); - - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: taskCreatePermission }], - { credentials: credentials }, - ) - )[0]; - - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } + await checkPermission({ + credentials, + permissions: [taskCreatePermission], + permissionService: permissions, + }); const bodySchema = z.object({ template: z.unknown(), diff --git a/plugins/scaffolder-backend/src/util/checkPermissions.ts b/plugins/scaffolder-backend/src/util/checkPermissions.ts new file mode 100644 index 0000000000..42d841ce2b --- /dev/null +++ b/plugins/scaffolder-backend/src/util/checkPermissions.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + BackstageCredentials, + PermissionsService, +} from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; +import { + AuthorizeResult, + ResourcePermission, +} from '@backstage/plugin-permission-common'; + +export type checkPermissionOptions = { + credentials: BackstageCredentials; + permissions: ResourcePermission[]; + permissionService?: PermissionsService; +}; + +/** + * Does a basic check on permissions. Throws 403 error if any permission responds with AuthorizeResult.DENY + * @public + */ +export async function checkPermission(options: checkPermissionOptions) { + const { permissions, permissionService, credentials } = options; + if (permissionService) { + const permissionRequest = permissions.map(resourcePermission => ({ + permission: resourcePermission, + })); + const authorizationResponses = await permissionService.authorizeConditional( + permissionRequest, + { credentials: credentials }, + ); + + for (const response of authorizationResponses) { + if (response.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + } +} From a2a6a826d82e4fb578b8e0d1f9b662ba42bbcfed Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 10:29:44 -0400 Subject: [PATCH 177/567] chore: remove unused imports Signed-off-by: Frank Kong --- plugins/scaffolder-backend/src/service/router.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3c98adc3f4..0d55d20c9e 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -30,12 +30,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { - InputError, - NotAllowedError, - NotFoundError, - stringifyError, -} from '@backstage/errors'; +import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { @@ -79,10 +74,7 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; -import { - AuthorizeResult, - PermissionRuleParams, -} from '@backstage/plugin-permission-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { createConditionAuthorizer, createPermissionIntegrationRouter, From 244b57722ac91d28562f202dc1dabcbeb21308fc Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Thu, 2 May 2024 16:35:04 +0200 Subject: [PATCH 178/567] added info block as suggested to inform permissions are setup when using create-app, reworded section Signed-off-by: Peter Macdonald --- docs/permissions/getting-started.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index 33e2f95041..33eea296d9 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -24,9 +24,13 @@ Like many other parts of Backstage, the permissions framework relies on informat ## Integrating the permission framework with your Backstage instance +:::info +If you created your backstage app using the [@backstage/create-app](https://backstage.io/docs/getting-started/#1-create-your-backstage-app), the permission framework will already be setup including the allow-all policy! +::: + ### 1. Set up the permission backend -The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: +The permissions framework uses the `permission-backend` plugin to accept authorization requests from other plugins across your Backstage deployment. The default `@backstage/create-app` template includes the permission backend, but if you need to make the change manually, these are the steps: 1. Add `@backstage/plugin-permission-backend` and `@backstage/plugin-permission-backend-module-allow-all-policy` to your backend dependencies, this will add the permission backend and a policy that allows all permissions: From e028d2793863e7a3a800bb12642e256d7f7dfac0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 15:13:19 +0000 Subject: [PATCH 179/567] chore(deps): pin actions/setup-python action to 82c7e63 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/verify_microsite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 68378aa086..7108d176c2 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18.x - - uses: actions/setup-python@v5 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: python-version: '3.9' From d675d6464174981488d145922acb9d4265b5936d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 15:13:59 +0000 Subject: [PATCH 180/567] chore(deps): update github artifact actions to v4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes.yml | 4 ++-- .github/workflows/pr-review-comment-trigger.yaml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/uffizzi-build.yml | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 07206718bb..7834fd195f 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -42,14 +42,14 @@ jobs: yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md - name: Upload Rendered Comment as Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: comment.md retention-days: 2 - name: Upload PR Event as Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: preview-spec path: ${{ github.event_path }} diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index 46f7dad8a3..4d7d511cc8 100644 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ b/.github/workflows/pr-review-comment-trigger.yaml @@ -30,7 +30,7 @@ jobs: run: | mkdir -p ./pr echo $PR_NUMBER > ./pr/pr_number - - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: pr_number-${{ github.event.pull_request.number }} path: pr/ diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 91f1b02fee..355ecf6727 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -58,7 +58,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: 'Upload artifact' - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: SARIF file path: results.sarif diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 4a52af21bc..7ccf39489e 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -98,13 +98,13 @@ jobs: kustomize build . > manifests.rendered.yml cat manifests.rendered.yml - name: Upload Rendered Manifests File as Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: ./.github/uffizzi/k8s/manifests/manifests.rendered.yml retention-days: 2 - name: Upload PR Event as Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: preview-spec path: ${{ github.event_path }} @@ -122,7 +122,7 @@ jobs: # If this PR is closing, we will not render a compose file nor pass it to the next workflow. - name: Upload PR Event as Artifact - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: preview-spec path: ${{ github.event_path }} From f2a2a83b8df7378752c0ae09bc4c23d5f4578075 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 2 May 2024 17:55:54 +0200 Subject: [PATCH 181/567] catalog-node: update catalogAnalysisExtensionPoint Signed-off-by: Patrik Oldsberg --- .changeset/brave-carrots-glow.md | 5 +++ .changeset/early-starfishes-hammer.md | 5 +++ .changeset/happy-radios-kiss.md | 5 +++ .changeset/rich-adults-float.md | 5 +++ .changeset/tough-eggs-wink.md | 5 +++ .../src/module/githubCatalogModule.test.ts | 5 ++- .../src/module/githubCatalogModule.ts | 2 +- plugins/catalog-backend/api-report.md | 11 ++--- plugins/catalog-backend/src/deprecated.ts | 6 +++ plugins/catalog-backend/src/index.ts | 1 - .../src/ingestion/LocationAnalyzer.ts | 6 ++- .../catalog-backend/src/ingestion/index.ts | 17 -------- .../catalog-backend/src/ingestion/types.ts | 33 --------------- .../src/service/CatalogBuilder.ts | 2 +- .../src/service/CatalogPlugin.ts | 40 +++++++++---------- .../src/service/createRouter.test.ts | 2 +- .../src/service/createRouter.ts | 2 +- plugins/catalog-node/api-report-alpha.md | 5 ++- plugins/catalog-node/api-report.md | 9 +++++ plugins/catalog-node/src/extensions.ts | 12 +++++- plugins/catalog-node/src/processing/index.ts | 1 + plugins/catalog-node/src/processing/types.ts | 19 ++++++++- 22 files changed, 107 insertions(+), 91 deletions(-) create mode 100644 .changeset/brave-carrots-glow.md create mode 100644 .changeset/early-starfishes-hammer.md create mode 100644 .changeset/happy-radios-kiss.md create mode 100644 .changeset/rich-adults-float.md create mode 100644 .changeset/tough-eggs-wink.md delete mode 100644 plugins/catalog-backend/src/ingestion/index.ts delete mode 100644 plugins/catalog-backend/src/ingestion/types.ts diff --git a/.changeset/brave-carrots-glow.md b/.changeset/brave-carrots-glow.md new file mode 100644 index 0000000000..b4493bf4f6 --- /dev/null +++ b/.changeset/brave-carrots-glow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`. diff --git a/.changeset/early-starfishes-hammer.md b/.changeset/early-starfishes-hammer.md new file mode 100644 index 0000000000..abe17e85a2 --- /dev/null +++ b/.changeset/early-starfishes-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`. diff --git a/.changeset/happy-radios-kiss.md b/.changeset/happy-radios-kiss.md new file mode 100644 index 0000000000..1097c832d8 --- /dev/null +++ b/.changeset/happy-radios-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API. diff --git a/.changeset/rich-adults-float.md b/.changeset/rich-adults-float.md new file mode 100644 index 0000000000..5a9746b8f5 --- /dev/null +++ b/.changeset/rich-adults-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Updated to use the new `catalogAnalysisExtensionPoint` API. diff --git a/.changeset/tough-eggs-wink.md b/.changeset/tough-eggs-wink.md new file mode 100644 index 0000000000..52644e3620 --- /dev/null +++ b/.changeset/tough-eggs-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden. diff --git a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts index 0f466fefb7..24ee7f7108 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts @@ -37,7 +37,8 @@ describe('githubCatalogModule', () => { }; const analysisExtensionPoint = { - addLocationAnalyzer: jest.fn(), + setLocationAnalyzer: jest.fn(), + addScmLocationAnalyzer: jest.fn(), }; const runner = jest.fn(); @@ -81,7 +82,7 @@ describe('githubCatalogModule', () => { 'github-provider:default', ); expect(runner).not.toHaveBeenCalled(); - expect(analysisExtensionPoint.addLocationAnalyzer).toHaveBeenCalledWith( + expect(analysisExtensionPoint.addScmLocationAnalyzer).toHaveBeenCalledWith( expect.any(GithubLocationAnalyzer), ); }); diff --git a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts index 0b46e1a023..809a1a5094 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts @@ -56,7 +56,7 @@ export const githubCatalogModule = createBackendModule({ discovery, auth, }) { - analyzers.addLocationAnalyzer( + analyzers.addScmLocationAnalyzer( new GithubLocationAnalyzer({ discovery, config, diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 27c00257a5..1c65abf83e 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -41,6 +41,7 @@ import { EntityRelationSpec as EntityRelationSpec_2 } from '@backstage/plugin-ca import { EventBroker } from '@backstage/plugin-events-node'; import { GetEntitiesRequest } from '@backstage/catalog-client'; import { HttpAuthService } from '@backstage/backend-plugin-api'; +import { LocationAnalyzer as LocationAnalyzer_2 } from '@backstage/plugin-catalog-node'; import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common'; import { locationSpecToLocationEntity as locationSpecToLocationEntity_2 } from '@backstage/plugin-catalog-node'; import { locationSpecToMetadataName as locationSpecToMetadataName_2 } from '@backstage/plugin-catalog-node'; @@ -167,7 +168,7 @@ export class CatalogBuilder { setEntityDataParser(parser: CatalogProcessorParser_2): CatalogBuilder; setEventBroker(broker: EventBroker): CatalogBuilder; setFieldFormatValidators(validators: Partial): CatalogBuilder; - setLocationAnalyzer(locationAnalyzer: LocationAnalyzer): CatalogBuilder; + setLocationAnalyzer(locationAnalyzer: LocationAnalyzer_2): CatalogBuilder; setPlaceholderResolver( key: string, resolver: PlaceholderResolver_2, @@ -359,12 +360,8 @@ export class FileReaderProcessor implements CatalogProcessor_2 { ): Promise; } -// @public (undocumented) -export type LocationAnalyzer = { - analyzeLocation( - location: AnalyzeLocationRequest_2, - ): Promise; -}; +// @public @deprecated (undocumented) +export type LocationAnalyzer = LocationAnalyzer_2; // @public @deprecated export class LocationEntityProcessor implements CatalogProcessor_2 { diff --git a/plugins/catalog-backend/src/deprecated.ts b/plugins/catalog-backend/src/deprecated.ts index 72c3cad225..2d08d2ce0c 100644 --- a/plugins/catalog-backend/src/deprecated.ts +++ b/plugins/catalog-backend/src/deprecated.ts @@ -48,6 +48,7 @@ import { type PlaceholderResolverParams as _PlaceholderResolverParams, type PlaceholderResolverRead as _PlaceholderResolverRead, type PlaceholderResolverResolveUrl as _PlaceholderResolverResolveUrl, + type LocationAnalyzer as _LocationAnalyzer, type ScmLocationAnalyzer as _ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; import { @@ -177,6 +178,11 @@ export type LocationSpec = _LocationSpec; * @deprecated import from `@backstage/plugin-catalog-node` instead */ export type AnalyzeOptions = _AnalyzeOptions; +/** + * @public + * @deprecated import from `@backstage/plugin-catalog-node` instead + */ +export type LocationAnalyzer = _LocationAnalyzer; /** * @public * @deprecated import from `@backstage/plugin-catalog-node` instead diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts index 6a479babdc..d3ffa9a974 100644 --- a/plugins/catalog-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -20,7 +20,6 @@ * @packageDocumentation */ -export * from './ingestion'; export * from './modules'; export * from './processing'; export * from './search'; diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 61d319bf37..d574f3b299 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -17,12 +17,14 @@ import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { LocationAnalyzer } from './types'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, } from '@backstage/plugin-catalog-common'; -import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node'; +import { + LocationAnalyzer, + ScmLocationAnalyzer, +} from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; export class RepoLocationAnalyzer implements LocationAnalyzer { diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts deleted file mode 100644 index a6555dd98d..0000000000 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export type { LocationAnalyzer } from './types'; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts deleted file mode 100644 index 6ca0ab259b..0000000000 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AnalyzeLocationRequest, - AnalyzeLocationResponse, -} from '@backstage/plugin-catalog-common'; - -/** @public */ -export type LocationAnalyzer = { - /** - * Generates an entity configuration for given git repository. It's used for - * importing new component to the backstage app. - * - * @param location - Git repository to analyze and generate config for. - */ - analyzeLocation( - location: AnalyzeLocationRequest, - ): Promise; -}; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 2438dc9009..f305bf2d8b 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -45,6 +45,7 @@ import { EntitiesSearchFilter, EntityProvider, PlaceholderResolver, + LocationAnalyzer, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; import { @@ -64,7 +65,6 @@ import { yamlPlaceholderResolver, } from '../modules/core/PlaceholderProcessor'; import { defaultEntityDataParser } from '../modules/util/parse'; -import { LocationAnalyzer } from '../ingestion'; import { CatalogProcessingEngine, createRandomProcessingInterval, diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index dd44e20add..6171ebbc1c 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -20,7 +20,6 @@ import { import { Entity, Validators } from '@backstage/catalog-model'; import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder'; import { - CatalogAnalysisExtensionPoint, catalogAnalysisExtensionPoint, CatalogModelExtensionPoint, catalogModelExtensionPoint, @@ -33,6 +32,7 @@ import { CatalogProcessor, CatalogProcessorParser, EntityProvider, + LocationAnalyzer, PlaceholderResolver, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; @@ -96,20 +96,6 @@ class CatalogProcessingExtensionPointImpl } } -class CatalogAnalysisExtensionPointImpl - implements CatalogAnalysisExtensionPoint -{ - #locationAnalyzers = new Array(); - - addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void { - this.#locationAnalyzers.push(analyzer); - } - - get locationAnalyzers() { - return this.#locationAnalyzers; - } -} - class CatalogPermissionExtensionPointImpl implements CatalogPermissionExtensionPoint { @@ -178,11 +164,19 @@ export const catalogPlugin = createBackendPlugin({ processingExtensions, ); - const analysisExtensions = new CatalogAnalysisExtensionPointImpl(); - env.registerExtensionPoint( - catalogAnalysisExtensionPoint, - analysisExtensions, - ); + let locationAnalyzer: LocationAnalyzer | undefined = undefined; + const scmLocationAnalyzers = new Array(); + env.registerExtensionPoint(catalogAnalysisExtensionPoint, { + setLocationAnalyzer(analyzer: LocationAnalyzer) { + if (locationAnalyzer) { + throw new Error('LocationAnalyzer has already been set'); + } + locationAnalyzer = analyzer; + }, + addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer) { + scmLocationAnalyzers.push(analyzer); + }, + }); const permissionExtensions = new CatalogPermissionExtensionPointImpl(); env.registerExtensionPoint( @@ -246,7 +240,11 @@ export const catalogPlugin = createBackendPlugin({ Object.entries(processingExtensions.placeholderResolvers).forEach( ([key, resolver]) => builder.setPlaceholderResolver(key, resolver), ); - builder.addLocationAnalyzers(...analysisExtensions.locationAnalyzers); + if (locationAnalyzer) { + builder.setLocationAnalyzer(locationAnalyzer); + } else { + builder.addLocationAnalyzers(...scmLocationAnalyzers); + } builder.addPermissions(...permissionExtensions.permissions); builder.addPermissionRules(...permissionExtensions.permissionRules); builder.setFieldFormatValidators(modelExtensions.fieldValidators); diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 292c4c1b7a..a2fae0e8bd 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -42,7 +42,7 @@ import { decodeCursor, encodeCursor } from './util'; import { wrapInOpenApiTestServer } from '@backstage/backend-openapi-utils'; import { Server } from 'http'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; -import { LocationAnalyzer } from '../ingestion'; +import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; describe('createRouter readonly disabled', () => { let entitiesCatalog: jest.Mocked; diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 508f0bb3d6..774ee61a6d 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -28,7 +28,6 @@ import express from 'express'; import yn from 'yn'; import { z } from 'zod'; import { EntitiesCatalog } from '../catalog/types'; -import { LocationAnalyzer } from '../ingestion'; import { CatalogProcessingOrchestrator } from '../processing/types'; import { validateEntityEnvelope } from '../processing/util'; import { @@ -55,6 +54,7 @@ import { HttpAuthService, LoggerService, } from '@backstage/backend-plugin-api'; +import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; /** * Options used by {@link createRouter}. diff --git a/plugins/catalog-node/api-report-alpha.md b/plugins/catalog-node/api-report-alpha.md index fb41d6f03e..cc3f7e5fb5 100644 --- a/plugins/catalog-node/api-report-alpha.md +++ b/plugins/catalog-node/api-report-alpha.md @@ -10,6 +10,7 @@ import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node'; import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; import { Permission } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; @@ -20,8 +21,8 @@ import { Validators } from '@backstage/catalog-model'; // @alpha (undocumented) export interface CatalogAnalysisExtensionPoint { - // (undocumented) - addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; + addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; + setLocationAnalyzer(analyzer: LocationAnalyzer): void; } // @alpha (undocumented) diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index a4ba95073f..7c1c8b5911 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -6,6 +6,8 @@ /// import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; @@ -166,6 +168,13 @@ export type EntityRelationSpec = { target: CompoundEntityRef; }; +// @public (undocumented) +export type LocationAnalyzer = { + analyzeLocation( + location: AnalyzeLocationRequest, + ): Promise; +}; + // @public @deprecated export type LocationSpec = LocationSpec_2; diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 648b8c5974..56478ec1db 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -22,6 +22,7 @@ import { EntitiesSearchFilter, EntityProvider, PlaceholderResolver, + LocationAnalyzer, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; import { @@ -79,7 +80,16 @@ export const catalogProcessingExtensionPoint = * @alpha */ export interface CatalogAnalysisExtensionPoint { - addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; + /** + * Replaces the entire location analyzer with a new one. This will cause any + * SCM analyzers added through `addScmLocationAnalyzer` to be ignored. + */ + setLocationAnalyzer(analyzer: LocationAnalyzer): void; + + /** + * Adds an analyzer for a specific SCM type to the default location analyzer. + */ + addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; } /** diff --git a/plugins/catalog-node/src/processing/index.ts b/plugins/catalog-node/src/processing/index.ts index 8cffac6961..4e83e6077d 100644 --- a/plugins/catalog-node/src/processing/index.ts +++ b/plugins/catalog-node/src/processing/index.ts @@ -21,5 +21,6 @@ export type { PlaceholderResolverParams, PlaceholderResolverRead, PlaceholderResolverResolveUrl, + LocationAnalyzer, ScmLocationAnalyzer, } from './types'; diff --git a/plugins/catalog-node/src/processing/types.ts b/plugins/catalog-node/src/processing/types.ts index b6785c1b91..e3d3c305fc 100644 --- a/plugins/catalog-node/src/processing/types.ts +++ b/plugins/catalog-node/src/processing/types.ts @@ -15,7 +15,11 @@ */ import { Entity } from '@backstage/catalog-model'; -import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; +import { + AnalyzeLocationExistingEntity, + AnalyzeLocationRequest, + AnalyzeLocationResponse, +} from '@backstage/plugin-catalog-common'; import { JsonValue } from '@backstage/types'; import { CatalogProcessorEmit } from '../api'; @@ -52,6 +56,19 @@ export type PlaceholderResolver = ( params: PlaceholderResolverParams, ) => Promise; +/** @public */ +export type LocationAnalyzer = { + /** + * Generates an entity configuration for given git repository. It's used for + * importing new component to the backstage app. + * + * @param location - Git repository to analyze and generate config for. + */ + analyzeLocation( + location: AnalyzeLocationRequest, + ): Promise; +}; + /** @public */ export type AnalyzeOptions = { url: string; From e5b903599f676edda9ac4d51419a5a931bd3a8d0 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 12:07:30 -0400 Subject: [PATCH 182/567] chore: update unit tests Signed-off-by: Frank Kong --- plugins/scaffolder-backend/src/service/router.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 2118f46b22..4443b28d09 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -895,6 +895,11 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ it('filters steps that the user is not authorized to see', async () => { jest .spyOn(permissionApi, 'authorizeConditional') + .mockImplementationOnce(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + ]) .mockImplementation(async () => [ { result: AuthorizeResult.ALLOW, From 0445f5309a3eaaf675275b981b68fcaeb439b959 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Thu, 2 May 2024 19:41:59 +0200 Subject: [PATCH 183/567] removes info block Signed-off-by: Peter Macdonald --- docs/permissions/getting-started.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index 33eea296d9..825fefe598 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -24,10 +24,6 @@ Like many other parts of Backstage, the permissions framework relies on informat ## Integrating the permission framework with your Backstage instance -:::info -If you created your backstage app using the [@backstage/create-app](https://backstage.io/docs/getting-started/#1-create-your-backstage-app), the permission framework will already be setup including the allow-all policy! -::: - ### 1. Set up the permission backend The permissions framework uses the `permission-backend` plugin to accept authorization requests from other plugins across your Backstage deployment. The default `@backstage/create-app` template includes the permission backend, but if you need to make the change manually, these are the steps: From 45446a0bfa7e1d8b902dccc9f0ed19e0e5ffdc6d Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Thu, 2 May 2024 16:08:36 -0300 Subject: [PATCH 184/567] feat: revert chnages done in EntityPresentationApi Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .changeset/olive-rockets-drum.md | 2 +- .../EntityPresentationApi.ts | 5 --- .../DefaultEntityPresentationApi.test.ts | 45 ------------------- .../DefaultEntityPresentationApi.ts | 27 +---------- 4 files changed, 2 insertions(+), 77 deletions(-) diff --git a/.changeset/olive-rockets-drum.md b/.changeset/olive-rockets-drum.md index 12f80dab5c..b5e6c2cefe 100644 --- a/.changeset/olive-rockets-drum.md +++ b/.changeset/olive-rockets-drum.md @@ -4,4 +4,4 @@ '@backstage/plugin-catalog': minor --- -`MultiEntityPicker` uses `entityPresentationApi` instead of `humanizeEntityRef` to display entity. Also, `EntityPresentationApi` now allows `promise` getter for under asynchronous process of presentation api +`MultiEntityPicker` uses `EntityDisplayName` instead of `humanizeEntityRef` to display entity. diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts index f4f1193718..945bce1d27 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts @@ -109,11 +109,6 @@ export interface EntityRefPresentation { * elsewhere. */ update$?: Observable; - - /* The `promise` property in the `EntityRefPresentation` interface is defining a property named - `promise` that holds a promise. This promise resolves to an array of - `EntityRefPresentationSnapshot` objects. */ - promise?: Promise; } /** diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts index c37efb204f..42ff777cbd 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts @@ -35,7 +35,6 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise(resolve => resolve({})), }); expect( @@ -49,7 +48,6 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise(resolve => resolve({})), }); expect( @@ -65,7 +63,6 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise(resolve => resolve({})), }); const entity: Entity = { @@ -88,7 +85,6 @@ describe('DefaultEntityPresentationApi', () => { Icon: expect.anything(), }, update$: undefined, - promise: new Promise(resolve => resolve({})), }); }); @@ -154,47 +150,6 @@ describe('DefaultEntityPresentationApi', () => { }), ); }); - - it('returns the correct snapshots via promise', async () => { - const catalogApi = { - getEntitiesByRefs: jest.fn(), - }; - const api = DefaultEntityPresentationApi.create({ - catalogApi: catalogApi as Partial as any, - }); - - catalogApi.getEntitiesByRefs.mockResolvedValueOnce({ - items: [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: 'test', - namespace: 'default', - etag: 'something', - }, - spec: { - type: 'service', - }, - }, - ], - }); - - const entityRef = 'component:default/test'; - const entitySnapshot = { - entityRef: entityRef, - primaryTitle: 'test', - secondaryTitle: 'component:default/test | service', - Icon: expect.anything(), - }; - - const promise = api.forEntity(entityRef).promise; - - const snapshots = await promise; - - expect(snapshots?.length).toEqual(1); // Only one snapshot expected - expect(snapshots?.[0]).toEqual(entitySnapshot); // Snapshot should match the simulated one - }); }); async function consumePresentation( diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts index c7572cca3b..82862be435 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts @@ -298,35 +298,10 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { }; }); - const entityRefPresentation: EntityRefPresentation = { + return { snapshot: initialSnapshot, update$: observable, - get promise() { - return new Promise(resolve => { - if (!observable) { - resolve([initialSnapshot]); - } else { - const res: EntityRefPresentationSnapshot[] = []; - const subscription = observable.subscribe({ - next: snapshot => { - res.push(snapshot); - }, - error: () => { - initialSnapshot = { - primaryTitle: entityRef, - entityRef: entityRef, - }; - }, - complete() { - subscription.unsubscribe(); - resolve(res); - }, - }); - } - }); - }, }; - return entityRefPresentation; } #getEntityForInitialRender(entityOrRef: Entity | string): { From 6cc3a9f66f237dd61a1dc85760fe57340c16fe75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 19:48:42 +0000 Subject: [PATCH 185/567] chore(deps): update peter-evans/find-comment digest to 3eae4d3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes-comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index 4c7fa452e7..bc90a276f0 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -94,7 +94,7 @@ jobs: # Identify comment to be updated - name: Find comment for API Changes - uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3 id: find-comment with: issue-number: ${{ needs.setup.outputs.pr-number }} From 8ade1283af574c503d1bf98db642eaa39f6142bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 19:48:57 +0000 Subject: [PATCH 186/567] chore(deps): update actions/checkout action to v4.1.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes.yml | 2 +- .github/workflows/automate_changeset_feedback.yml | 2 +- .github/workflows/automate_merge_message.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/deploy_docker-image.yml | 2 +- .github/workflows/deploy_microsite.yml | 2 +- .github/workflows/deploy_nightly.yml | 2 +- .github/workflows/deploy_packages.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_code-formatting.yml | 2 +- .github/workflows/sync_dependabot-changesets.yml | 2 +- .github/workflows/sync_release-manifest.yml | 4 ++-- .github/workflows/sync_renovate-changesets.yml | 2 +- .github/workflows/sync_snyk-github-issues.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/sync_version-packages.yml | 2 +- .github/workflows/uffizzi-build.yml | 4 ++-- .github/workflows/verify_accessibility.yml | 2 +- .github/workflows/verify_codeql.yml | 2 +- .github/workflows/verify_docs-quality.yml | 2 +- .github/workflows/verify_e2e-kubernetes.yml | 2 +- .github/workflows/verify_e2e-linux.yml | 2 +- .github/workflows/verify_e2e-techdocs.yml | 2 +- .github/workflows/verify_e2e-windows.yml | 2 +- .github/workflows/verify_fossa.yml | 2 +- .github/workflows/verify_microsite.yml | 2 +- .github/workflows/verify_microsite_accessibility.yml | 2 +- .github/workflows/verify_storybook.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- 29 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 07206718bb..901516305d 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 4d965499d2..fefc73bd9f 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 38243ee0ac..d6520d16a6 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdd31b2d57..c819ae8130 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -68,7 +68,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -197,7 +197,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: fetch master branch run: git fetch origin master diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 020254c872..8c263fdd4f 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -25,7 +25,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 6864ec67dc..b955b39b59 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 07c3db0f11..a3a1b5e283 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index f9ef4c67eb..0c44d32384 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -65,7 +65,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -148,7 +148,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 67fdff4020..5acd11c9b0 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -34,7 +34,7 @@ jobs: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: persist-credentials: false diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 5dbd0361c1..a59eea9c9b 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -14,7 +14,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 8b3be335ed..88997ea5dd 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index b0a2499d43..1549ec0e90 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -21,7 +21,7 @@ jobs: run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1 - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: backstage # 'v' prefix is added here for the tag, we keep it out of the manifest logic @@ -29,7 +29,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: repository: backstage/versions path: backstage/versions diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index d02e35483c..e95a68ac2f 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index d5a4048044..bebbbc4fbe 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index e2507996b1..cb3efcfac3 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -29,7 +29,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master with: diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index b2ad80b6da..82a0eae7be 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 20000 fetch-tags: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 4a52af21bc..11a0054703 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: setup-node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -89,7 +89,7 @@ jobs: egress-policy: audit - name: Checkout git repo - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Render Compose File run: | # update image after the build above diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 1c25d709cb..b4615fee0a 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -24,7 +24,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 920c9bab17..60cb89a045 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -47,7 +47,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 3550df6fc0..2eb7608ccd 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # Vale does not support file excludes, so we use the script to generate a list of files instead # The action also does not allow args or a local config file to be passed in, so the files array diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index 6ff41c8b74..3c425c25e9 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -26,7 +26,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index a750dfea3b..75c7a800d8 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -45,7 +45,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Configure Git run: | diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 3bcda5f2b6..d5f2594285 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -34,7 +34,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 078ddd6855..a6fa469666 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -42,7 +42,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Configure Git run: | diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 801c5fbd28..ccc9e4509a 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -19,7 +19,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 68378aa086..db6a3e2d40 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index e0fea1a72c..19417c5cee 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 940c136797..6a37f5abb6 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 0 # Required to retrieve git history diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 32b0452eec..c7ff8f5ca9 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -33,7 +33,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 From 5a0c7cdbfa5c50ee4c5acdec26c9a2fdd852f9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 2 May 2024 23:03:06 +0200 Subject: [PATCH 187/567] don't redo the rendering in both observable and promise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../DefaultEntityPresentationApi.ts | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts index 9630c6c216..1955763d3d 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts @@ -267,35 +267,38 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { }; } - const loadingPromise = Promise.resolve().then(() => - this.#loader?.load(entityRef), - ); + // Load the entity and render it + const maybeUpdatedSnapshot = Promise.resolve() + .then(() => { + return this.#loader?.load(entityRef); + }) + .then(newEntity => { + // We re-render no matter if we get back a new entity or the old + // one or nothing, because of the now false loading state - in + // case the renderer outputs different data depending on that + return render({ + loading: false, + entity: newEntity ?? entity, + }); + }) + .catch(() => { + // Intentionally ignored - we do not propagate errors to the + // caller here. The presentation API should be error free and + // always return SOMETHING that makes sense to render, and we have + // already ensured above that the initial snapshot was that. + return undefined; + }); - // And then the following snapshot const observable = new ObservableImpl( subscriber => { let aborted = false; - loadingPromise - .then(newEntity => { - if ( - !aborted && - newEntity && - newEntity.metadata.etag !== entity?.metadata.etag - ) { - const updatedSnapshot = render({ - loading: false, - entity: newEntity, - }); + maybeUpdatedSnapshot + .then(updatedSnapshot => { + if (updatedSnapshot) { subscriber.next(updatedSnapshot); } }) - .catch(() => { - // Intentionally ignored - we do not propagate errors to the - // observable here. The presentation API should be error free and - // always return SOMETHING that makes sense to render, and we have - // already ensured above that the initial snapshot was that. - }) .finally(() => { if (!aborted) { subscriber.complete(); @@ -308,17 +311,9 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { }, ); - const promise = loadingPromise - .then(newEntity => { - if (newEntity && newEntity.metadata.etag !== entity?.metadata.etag) { - return render({ - loading: false, - entity: newEntity, - }); - } - return initialSnapshot; - }) - .catch(() => initialSnapshot); + const promise = maybeUpdatedSnapshot.then(updatedSnapshot => { + return updatedSnapshot ?? initialSnapshot; + }); return { snapshot: initialSnapshot, From ea7cb44de53845959c6f9acec2f1a4d21b66404f Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 22:36:30 -0400 Subject: [PATCH 188/567] chore: apply suggestions Signed-off-by: Frank Kong --- plugins/catalog/package.json | 2 +- .../src/components/AboutCard/AboutCard.tsx | 3 +- .../tasks/NunjucksWorkflowRunner.ts | 2 +- .../scaffolder-backend/src/service/router.ts | 38 ++++++++++++------- .../src/util/checkPermissions.ts | 6 +-- plugins/scaffolder-common/src/permissions.ts | 18 +-------- .../components/TemplateCard/TemplateCard.tsx | 1 - .../components/OngoingTask/ContextMenu.tsx | 9 +---- .../components/OngoingTask/OngoingTask.tsx | 8 +--- yarn.lock | 4 +- 10 files changed, 36 insertions(+), 55 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index da973aadb5..d22d9778e6 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -89,7 +89,7 @@ "@testing-library/react": "^15.0.0", "@testing-library/user-event": "^14.0.0", "@types/pluralize": "^0.0.33", - "swr": "^2.0.0" + "swr": "^2.2.5" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index b63666f80c..ee6147c54e 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -116,10 +116,9 @@ export function AboutCard(props: AboutCardProps) { const { allowed: canRefresh } = useEntityPermission( catalogEntityRefreshPermission, ); - const { kind, name, namespace } = entity.metadata; + const { allowed: canCreateTemplateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: `${kind}:${namespace}/${name}`, }); const entitySourceLocation = getEntitySourceLocation( diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 5d24ac7cc7..99eb7c7bb5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -31,6 +31,7 @@ import { SecureTemplateRenderer, } from '../../lib/templating/SecureTemplater'; import { + TaskRecovery, TaskSpec, TaskSpecV1beta3, TaskStep, @@ -52,7 +53,6 @@ import { } from '@backstage/plugin-permission-common'; import { scaffolderActionRules } from '../../service/rules'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; -import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { loggerToWinstonLogger } from '@backstage/backend-common'; import { BackstageLoggerTransport, WinstonLogger } from './logger'; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 0d55d20c9e..287d058030 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -30,7 +30,12 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; +import { + InputError, + NotAllowedError, + NotFoundError, + stringifyError, +} from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { @@ -43,7 +48,6 @@ import { import { RESOURCE_TYPE_SCAFFOLDER_ACTION, RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, - RESOURCE_TYPE_SCAFFOLDER_TASK, scaffolderActionPermissions, scaffolderTemplatePermissions, taskCancelPermission, @@ -74,7 +78,10 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; -import { PermissionRuleParams } from '@backstage/plugin-permission-common'; +import { + AuthorizeResult, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { createConditionAuthorizer, createPermissionIntegrationRouter, @@ -97,11 +104,7 @@ import { import { InternalTaskSecrets } from '../scaffolder/tasks/types'; import { checkPermission } from '../util/checkPermissions'; -/** - * - * @public - */ -export type ScaffolderPermissionRuleInput = +type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput | ActionPermissionRuleInput | TaskPermissionRuleInput; @@ -440,7 +443,7 @@ export async function createRouter( rules: actionRules, }, { - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, + resourceType: 'basic', permissions: scaffolderTaskPermissions, rules: taskRules, }, @@ -485,11 +488,18 @@ export async function createRouter( ) .get('/v2/actions', async (req, res) => { const credentials = await httpAuth.credentials(req); - await checkPermission({ - credentials, - permissions: [actionReadPermission], - permissionService: permissions, - }); + if (permissions) { + const authorizationResponse = ( + await permissions.authorizeConditional( + [{ permission: actionReadPermission }], + { credentials: credentials }, + ) + )[0]; + if (authorizationResponse.result === AuthorizeResult.DENY) { + throw new NotAllowedError(); + } + } + const actionsList = actionRegistry.list().map(action => { return { id: action.id, diff --git a/plugins/scaffolder-backend/src/util/checkPermissions.ts b/plugins/scaffolder-backend/src/util/checkPermissions.ts index 42d841ce2b..40aa673b3f 100644 --- a/plugins/scaffolder-backend/src/util/checkPermissions.ts +++ b/plugins/scaffolder-backend/src/util/checkPermissions.ts @@ -20,12 +20,12 @@ import { import { NotAllowedError } from '@backstage/errors'; import { AuthorizeResult, - ResourcePermission, + BasicPermission, } from '@backstage/plugin-permission-common'; export type checkPermissionOptions = { credentials: BackstageCredentials; - permissions: ResourcePermission[]; + permissions: BasicPermission[]; permissionService?: PermissionsService; }; @@ -39,7 +39,7 @@ export async function checkPermission(options: checkPermissionOptions) { const permissionRequest = permissions.map(resourcePermission => ({ permission: resourcePermission, })); - const authorizationResponses = await permissionService.authorizeConditional( + const authorizationResponses = await permissionService.authorize( permissionRequest, { credentials: credentials }, ); diff --git a/plugins/scaffolder-common/src/permissions.ts b/plugins/scaffolder-common/src/permissions.ts index 4a4137875a..6bd7e130ec 100644 --- a/plugins/scaffolder-common/src/permissions.ts +++ b/plugins/scaffolder-common/src/permissions.ts @@ -30,13 +30,6 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; */ export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; -/** - * Permission resource type which corresponds to a scaffolder task. - * - * @alpha - */ -export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; - /** * This permission is used to authorize actions that involve executing * an action from a template. @@ -49,6 +42,7 @@ export const actionExecutePermission = createPermission({ resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, }); +// TODO: Figure out whether to convert this to a basic permission or remove it completely since the current rules aren't applicable to this permission /** * This permission is used to authorize actions that involve access the action registry * @@ -101,8 +95,6 @@ export const templateStepReadPermission = createPermission({ * This permission is used to authorize actions that involve reading one or more tasks in the scaffolder, * and reading logs of tasks * - * Task cancellation would also require this permission. - * * @alpha */ export const taskReadPermission = createPermission({ @@ -110,7 +102,6 @@ export const taskReadPermission = createPermission({ attributes: { action: 'read', }, - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** @@ -123,20 +114,16 @@ export const taskCreatePermission = createPermission({ attributes: { action: 'create', }, - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** - * This permission us used to authorize actions that involve the cancellation of tasks in the scaffolder. - * - * This will require the `scaffolder.task.read` permission to be authorized. + * This permission is used to authorize actions that involve the cancellation of tasks in the scaffolder. * * @alpha */ export const taskCancelPermission = createPermission({ name: 'scaffolder.task.cancel', attributes: {}, - resourceType: RESOURCE_TYPE_SCAFFOLDER_TASK, }); /** @@ -144,7 +131,6 @@ export const taskCancelPermission = createPermission({ * @alpha */ export const scaffolderPermissions = [ - actionExecutePermission, templateParameterReadPermission, templateStepReadPermission, ]; diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx index 253d588862..5d2c8aa7fe 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx @@ -112,7 +112,6 @@ export const TemplateCard = (props: TemplateCardProps) => { const { allowed: canCreateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: 'task', }); const handleChoose = useCallback(() => { analytics.captureEvent('click', `Template has been opened`); diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 1a5cccf142..f96f83fd2f 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -77,25 +77,18 @@ export const ContextMenu = (props: ContextMenuProps) => { } }); - // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` const { allowed: canCancelTask } = usePermission({ permission: taskCancelPermission, - resourceRef: 'task', }); const { allowed: canReadTask } = usePermission({ permission: taskReadPermission, - resourceRef: 'task', }); const { allowed: canCreateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: 'task', }); - // Cancel endpoint requires user to have both read and cancel permissions - const cancelNotAllowed = !(canReadTask && canCancelTask); - // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions const canStartOver = canReadTask && canCreateTask; @@ -150,7 +143,7 @@ export const ContextMenu = (props: ContextMenuProps) => { disabled={ !cancelEnabled || cancelStatus !== 'not-executed' || - cancelNotAllowed + !canCancelTask } data-testid="cancel-task" > diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index b7af4b6618..f4099028ba 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -91,22 +91,16 @@ export const OngoingTask = (props: { // Used dummy string value for `resourceRef` since `allowed` field will always return `false` if `resourceRef` is `undefined` const { allowed: canCancelTask } = usePermission({ permission: taskCancelPermission, - resourceRef: 'task', }); const { allowed: canReadTask } = usePermission({ permission: taskReadPermission, - resourceRef: 'task', }); const { allowed: canCreateTask } = usePermission({ permission: taskCreatePermission, - resourceRef: 'task', }); - // Cancel endpoint requires user to have both read and cancel permissions - const cancelNotAllowed = !(canReadTask && canCancelTask); - // Start Over endpoint requires user to have both read (to grab parameters) and create (to create new task) permissions const canStartOver = canReadTask && canCreateTask; @@ -228,7 +222,7 @@ export const OngoingTask = (props: { disabled={ !cancelEnabled || cancelStatus !== 'not-executed' || - cancelNotAllowed + !canCancelTask } onClick={triggerCancel} data-testid="cancel-button" diff --git a/yarn.lock b/yarn.lock index bf16ce2937..f5d34f9c95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5684,7 +5684,7 @@ __metadata: lodash: ^4.17.21 pluralize: ^8.0.0 react-use: ^17.2.4 - swr: ^2.0.0 + swr: ^2.2.5 zen-observable: ^0.10.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -39520,7 +39520,7 @@ __metadata: languageName: node linkType: hard -"swr@npm:^2.0.0": +"swr@npm:^2.0.0, swr@npm:^2.2.5": version: 2.2.5 resolution: "swr@npm:2.2.5" dependencies: From b75d78761cfa1876346de726ed5258da08c88fbb Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 22:53:12 -0400 Subject: [PATCH 189/567] chore(scaffolder-backend): update unit tests for router Signed-off-by: Frank Kong --- .../scaffolder-backend/src/service/router.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 4443b28d09..c129859c49 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -235,6 +235,11 @@ describe('createRouter', () => { result: AuthorizeResult.ALLOW, }, ]); + jest.spyOn(permissionApi, 'authorize').mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + ]); }); afterEach(() => { @@ -741,6 +746,11 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ result: AuthorizeResult.ALLOW, }, ]); + jest.spyOn(permissionApi, 'authorize').mockImplementation(async () => [ + { + result: AuthorizeResult.ALLOW, + }, + ]); }); afterEach(() => { @@ -895,11 +905,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ it('filters steps that the user is not authorized to see', async () => { jest .spyOn(permissionApi, 'authorizeConditional') - .mockImplementationOnce(async () => [ - { - result: AuthorizeResult.ALLOW, - }, - ]) .mockImplementation(async () => [ { result: AuthorizeResult.ALLOW, From e01a2e93caeaec35b95afa50189df1b2548d402c Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Thu, 2 May 2024 23:16:04 -0400 Subject: [PATCH 190/567] chore: fix tsc errors and update api-report Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 14 +-------- .../scaffolder-backend/src/service/router.ts | 30 +++++-------------- plugins/scaffolder-common/api-report-alpha.md | 17 ++++------- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index e99ac56bee..e6738497fe 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -36,7 +36,6 @@ import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; -import { RESOURCE_TYPE_SCAFFOLDER_TASK } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; import { Schema } from 'jsonschema'; @@ -502,8 +501,7 @@ export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; // @public (undocumented) export type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput - | ActionPermissionRuleInput - | TaskPermissionRuleInput; + | ActionPermissionRuleInput; // @public @deprecated export type SerializedTask = SerializedTask_2; @@ -580,16 +578,6 @@ export class TaskManager implements TaskContext_2 { ): Promise; } -// @public (undocumented) -export type TaskPermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_TASK, - TParams ->; - // @public @deprecated (undocumented) export type TaskSecrets = TaskSecrets_2; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 287d058030..ff1796e71f 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -104,10 +104,13 @@ import { import { InternalTaskSecrets } from '../scaffolder/tasks/types'; import { checkPermission } from '../util/checkPermissions'; -type ScaffolderPermissionRuleInput = +/** + * + * @public + */ +export type ScaffolderPermissionRuleInput = | TemplatePermissionRuleInput - | ActionPermissionRuleInput - | TaskPermissionRuleInput; + | ActionPermissionRuleInput; /** * @@ -145,23 +148,6 @@ function isActionPermissionRuleInput( return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; } -/** - * - * @public - */ -export type TaskPermissionRuleInput< - TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule< - TemplateEntityStepV1beta3 | TemplateParametersV1beta3, - {}, - typeof RESOURCE_TYPE_SCAFFOLDER_TASK, - TParams ->; -function isTaskPermissionRuleInput( - permissionRule: ScaffolderPermissionRuleInput, -): permissionRule is TaskPermissionRuleInput { - return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TASK; -} /** * RouterOptions * @@ -418,14 +404,12 @@ export async function createRouter( const actionRules: ActionPermissionRuleInput[] = Object.values( scaffolderActionRules, ); - const taskRules: TaskPermissionRuleInput[] = []; if (permissionRules) { templateRules.push( ...permissionRules.filter(isTemplatePermissionRuleInput), ); actionRules.push(...permissionRules.filter(isActionPermissionRuleInput)); - taskRules.push(...permissionRules.filter(isTaskPermissionRuleInput)); } const isAuthorized = createConditionAuthorizer(Object.values(templateRules)); @@ -445,7 +429,7 @@ export async function createRouter( { resourceType: 'basic', permissions: scaffolderTaskPermissions, - rules: taskRules, + rules: [], }, ], }); diff --git a/plugins/scaffolder-common/api-report-alpha.md b/plugins/scaffolder-common/api-report-alpha.md index 3762b0b773..a06f3123e7 100644 --- a/plugins/scaffolder-common/api-report-alpha.md +++ b/plugins/scaffolder-common/api-report-alpha.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BasicPermission } from '@backstage/plugin-permission-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha @@ -14,9 +15,6 @@ export const actionReadPermission: ResourcePermission<'scaffolder-action'>; // @alpha export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; -// @alpha -export const RESOURCE_TYPE_SCAFFOLDER_TASK = 'scaffolder-task'; - // @alpha export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; @@ -24,25 +22,22 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; export const scaffolderActionPermissions: ResourcePermission<'scaffolder-action'>[]; // @alpha -export const scaffolderPermissions: ( - | ResourcePermission<'scaffolder-action'> - | ResourcePermission<'scaffolder-template'> -)[]; +export const scaffolderPermissions: ResourcePermission<'scaffolder-template'>[]; // @alpha -export const scaffolderTaskPermissions: ResourcePermission<'scaffolder-task'>[]; +export const scaffolderTaskPermissions: BasicPermission[]; // @alpha export const scaffolderTemplatePermissions: ResourcePermission<'scaffolder-template'>[]; // @alpha -export const taskCancelPermission: ResourcePermission<'scaffolder-task'>; +export const taskCancelPermission: BasicPermission; // @alpha -export const taskCreatePermission: ResourcePermission<'scaffolder-task'>; +export const taskCreatePermission: BasicPermission; // @alpha -export const taskReadPermission: ResourcePermission<'scaffolder-task'>; +export const taskReadPermission: BasicPermission; // @alpha export const templateParameterReadPermission: ResourcePermission<'scaffolder-template'>; From 79a6358f50854b626d06598cbae0384e44c0b68f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 3 May 2024 10:40:49 +0200 Subject: [PATCH 191/567] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- .../building-apps/02-configuring-extensions.md | 2 +- docs/frontend-system/building-apps/08-migrating.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/frontend-system/building-apps/02-configuring-extensions.md b/docs/frontend-system/building-apps/02-configuring-extensions.md index 5f090e23cd..4052ef47f0 100644 --- a/docs/frontend-system/building-apps/02-configuring-extensions.md +++ b/docs/frontend-system/building-apps/02-configuring-extensions.md @@ -6,7 +6,7 @@ sidebar_label: Configuring Extensions description: Documentation for how to configure extensions in a Backstage app --- -All extensions in a Backstage app can be configured through static configuration. This configuration is all done under a the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md). +All extensions in a Backstage app can be configured through static configuration. This configuration is all done under the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md). ## Extension Configuration Schema diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 8bcc1f973f..dcc58325cf 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -117,7 +117,7 @@ You can then also add any additional extensions that you may need to create as p [Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). -For example, the following apis configuration: +For example, the following `apis` configuration: ```ts const app = createApp({ @@ -151,7 +151,7 @@ Icons are currently installed through the usual options to `createApp`, but will Plugins are now passed through the `features` options instead. -For example, the following plugins configuration: +For example, the following `plugins` configuration: ```tsx import { homePlugin } from '@backstage/plugin-home'; @@ -163,7 +163,7 @@ createApp({ }); ``` -Can be converted to the following features configuration: +Can be converted to the following `features` configuration: ```tsx // plugins are now default exported via alpha subpath From 110665bbd559a1fa22b27f3b7c68cbf71994f4c6 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Fri, 3 May 2024 10:49:35 +0200 Subject: [PATCH 192/567] wip Signed-off-by: bnechyporenko --- beps/0001-notifications-system/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beps/0001-notifications-system/README.md b/beps/0001-notifications-system/README.md index fb3e36f624..bde54208eb 100644 --- a/beps/0001-notifications-system/README.md +++ b/beps/0001-notifications-system/README.md @@ -110,7 +110,7 @@ The notification backend stores notification using the [database service](https: - Topic (optional) - Scope (optional) - Icon (optional) - - Extra (optional) + - Metadata (optional) The recipients is **not** a list of users, but rather a filter that describes who should receive the notification. It must be possible to evaluate this filter in a database query, so that we can efficiently fetch all notifications for a given user. The same filter will also be used by the signal backend to determine which users should receive a signal. @@ -148,7 +148,7 @@ The link is a relative or absolute URL. As an example, it can be used: - by an external system to request an action within an asynchronous task - by a BE plugin to provide link to other part of the Backstage UI (i.e. to the Catalog) -The extra is a flexible JSON like field, where an additional payload can be stored. +The metadata is a flexible JSON like field, where an additional payload can be stored. The additional links are an array of title-URL pairs. They can represent immediate actions on the notification (i.e. yes-no) or lead the user to additional details. From ae500129a9146c600453de465103f917b5fbbcb4 Mon Sep 17 00:00:00 2001 From: Nitin Ramnani Date: Fri, 3 May 2024 14:49:01 +0530 Subject: [PATCH 193/567] Added stories for multiple components Signed-off-by: Nitin Ramnani --- .../HeaderIconLinkRow.stories.tsx | 51 ++++++++++++++++ .../ResponseErrorPanel.stories.tsx | 38 ++++++++++++ .../layout/BottomLink/BottomLink.stories.tsx | 33 +++++++++++ .../ContentHeader/ContentHeader.stories.tsx | 58 +++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx create mode 100644 packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx create mode 100644 packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx create mode 100644 packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx diff --git a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx new file mode 100644 index 0000000000..e6891081e3 --- /dev/null +++ b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx @@ -0,0 +1,51 @@ +/* + * 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 { HeaderIconLinkRow } from '../HeaderIconLinkRow'; +import { IconLinkVerticalProps } from './IconLinkVertical'; + + +type Props = { + links: IconLinkVerticalProps[]; + }; + +export default { + title: 'Data Display/HeaderIconLinkRow', + component: HeaderIconLinkRow, +}; + + + +export const Default = (args:Props) => +Default.args = { + links: [ + { + color: 'primary', + disabled: false, + href: "https://google.com", + label: "primary", + title: "title" + }, + { + color: 'secondary', + disabled: false, + href: "https://google.com", + label: "secondary", + title: "title-2" + }, + ] +}; \ No newline at end of file diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx new file mode 100644 index 0000000000..62eb523a02 --- /dev/null +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx @@ -0,0 +1,38 @@ +/* + * 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 { ResponseErrorPanel } from '../ResponseErrorPanel'; +import { ErrorPanelProps } from '../ErrorPanel'; + +export default { + title: 'Data Display/ResponseErrorPanel', + component: ResponseErrorPanel, +}; + +export const Default = (args:ErrorPanelProps) => +Default.args = { + error: new Error('Error message from error object'), + defaultExpanded: false +}; + + +export const WithTitle = (args:ErrorPanelProps) => +WithTitle.args = { + error: new Error('test'), + defaultExpanded: false, + title:"Title prop is passed" +}; diff --git a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx new file mode 100644 index 0000000000..84f268681d --- /dev/null +++ b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import React from 'react'; +import { BottomLink } from '../BottomLink'; + +export default { + title: 'Layout/BottomLink', + component: BottomLink, +}; + +export const Default = (args:{ + link:string + title:string +}) => +Default.args = { + link: 'https://google.com', + title: 'This is bottom link' +}; diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx new file mode 100644 index 0000000000..ce6365c28e --- /dev/null +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx @@ -0,0 +1,58 @@ +/* + * 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 , {ReactNode} from 'react'; +import { ContentHeader } from '../ContentHeader'; + +export default { + title: 'Layout/ContentHeader', + component: ContentHeader, +}; + +type ContentHeaderProps = { + title?: string + titleComponent?: ReactNode; + description?: string; + textAlign?: 'left' | 'right' | 'center'; + }; + +export const Default = (args:ContentHeaderProps) =>
Child of Content Header
+Default.args = { + title: 'This is Content Header default aligned', + description:'This is description' +}; + + +export const Left = (args:ContentHeaderProps) =>
Child of Content Header
+Left.args = { + title: 'This is Content Header left aligned', + description:'This is description', + textAlign: 'left' +}; + +export const Right = (args:ContentHeaderProps) =>
Child of Content Header
+Right.args = { + title: 'This is Content Header right aligned', + description:'This is description', + textAlign: 'right' +}; + +export const Center = (args:ContentHeaderProps) =>
Child of Content Header
+Center.args = { + title: 'This is Content Header center aligned', + description:'This is description', + textAlign: 'center' +}; From baf298f57ec2a6fe1b43dd584cf78f2e0c959467 Mon Sep 17 00:00:00 2001 From: Nitin Ramnani Date: Fri, 3 May 2024 14:52:20 +0530 Subject: [PATCH 194/567] Added stories for multiple components Signed-off-by: Nitin Ramnani --- .../HeaderIconLinkRow.stories.tsx | 49 +++++++-------- .../ResponseErrorPanel.stories.tsx | 23 ++++--- .../layout/BottomLink/BottomLink.stories.tsx | 16 +++-- .../ContentHeader/ContentHeader.stories.tsx | 63 ++++++++++++------- 4 files changed, 82 insertions(+), 69 deletions(-) diff --git a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx index e6891081e3..e472ab5599 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx @@ -18,34 +18,31 @@ import React from 'react'; import { HeaderIconLinkRow } from '../HeaderIconLinkRow'; import { IconLinkVerticalProps } from './IconLinkVertical'; - type Props = { - links: IconLinkVerticalProps[]; - }; - -export default { - title: 'Data Display/HeaderIconLinkRow', - component: HeaderIconLinkRow, + links: IconLinkVerticalProps[]; }; +export default { + title: 'Data Display/HeaderIconLinkRow', + component: HeaderIconLinkRow, +}; - -export const Default = (args:Props) => +export const Default = (args: Props) => ; Default.args = { - links: [ - { - color: 'primary', - disabled: false, - href: "https://google.com", - label: "primary", - title: "title" - }, - { - color: 'secondary', - disabled: false, - href: "https://google.com", - label: "secondary", - title: "title-2" - }, - ] -}; \ No newline at end of file + links: [ + { + color: 'primary', + disabled: false, + href: 'https://google.com', + label: 'primary', + title: 'title', + }, + { + color: 'secondary', + disabled: false, + href: 'https://google.com', + label: 'secondary', + title: 'title-2', + }, + ], +}; diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx index 62eb523a02..8d03e7359f 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx @@ -19,20 +19,23 @@ import { ResponseErrorPanel } from '../ResponseErrorPanel'; import { ErrorPanelProps } from '../ErrorPanel'; export default { - title: 'Data Display/ResponseErrorPanel', - component: ResponseErrorPanel, + title: 'Data Display/ResponseErrorPanel', + component: ResponseErrorPanel, }; -export const Default = (args:ErrorPanelProps) => +export const Default = (args: ErrorPanelProps) => ( + +); Default.args = { - error: new Error('Error message from error object'), - defaultExpanded: false + error: new Error('Error message from error object'), + defaultExpanded: false, }; - -export const WithTitle = (args:ErrorPanelProps) => +export const WithTitle = (args: ErrorPanelProps) => ( + +); WithTitle.args = { - error: new Error('test'), - defaultExpanded: false, - title:"Title prop is passed" + error: new Error('test'), + defaultExpanded: false, + title: 'Title prop is passed', }; diff --git a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx index 84f268681d..e05af86993 100644 --- a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx +++ b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx @@ -14,20 +14,18 @@ * limitations under the License. */ - import React from 'react'; import { BottomLink } from '../BottomLink'; export default { - title: 'Layout/BottomLink', - component: BottomLink, + title: 'Layout/BottomLink', + component: BottomLink, }; -export const Default = (args:{ - link:string - title:string -}) => +export const Default = (args: { link: string; title: string }) => ( + +); Default.args = { - link: 'https://google.com', - title: 'This is bottom link' + link: 'https://google.com', + title: 'This is bottom link', }; diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx index ce6365c28e..27df00e72c 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx @@ -14,45 +14,60 @@ * limitations under the License. */ -import React , {ReactNode} from 'react'; +import React, { ReactNode } from 'react'; import { ContentHeader } from '../ContentHeader'; export default { - title: 'Layout/ContentHeader', - component: ContentHeader, + title: 'Layout/ContentHeader', + component: ContentHeader, }; type ContentHeaderProps = { - title?: string - titleComponent?: ReactNode; - description?: string; - textAlign?: 'left' | 'right' | 'center'; - }; + title?: string; + titleComponent?: ReactNode; + description?: string; + textAlign?: 'left' | 'right' | 'center'; +}; -export const Default = (args:ContentHeaderProps) =>
Child of Content Header
+export const Default = (args: ContentHeaderProps) => ( + +
Child of Content Header
+
+); Default.args = { - title: 'This is Content Header default aligned', - description:'This is description' + title: 'This is Content Header default aligned', + description: 'This is description', }; - -export const Left = (args:ContentHeaderProps) =>
Child of Content Header
+export const Left = (args: ContentHeaderProps) => ( + +
Child of Content Header
+
+); Left.args = { - title: 'This is Content Header left aligned', - description:'This is description', - textAlign: 'left' + title: 'This is Content Header left aligned', + description: 'This is description', + textAlign: 'left', }; -export const Right = (args:ContentHeaderProps) =>
Child of Content Header
+export const Right = (args: ContentHeaderProps) => ( + +
Child of Content Header
+
+); Right.args = { - title: 'This is Content Header right aligned', - description:'This is description', - textAlign: 'right' + title: 'This is Content Header right aligned', + description: 'This is description', + textAlign: 'right', }; -export const Center = (args:ContentHeaderProps) =>
Child of Content Header
+export const Center = (args: ContentHeaderProps) => ( + +
Child of Content Header
+
+); Center.args = { - title: 'This is Content Header center aligned', - description:'This is description', - textAlign: 'center' + title: 'This is Content Header center aligned', + description: 'This is description', + textAlign: 'center', }; From 2e20518cd2d16940fff1f8069e43593e0702e86a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 3 May 2024 11:43:07 +0200 Subject: [PATCH 195/567] catalog-node: allow location analyzer to be configured as a factory as well Signed-off-by: Patrik Oldsberg --- .../src/service/CatalogPlugin.ts | 26 +++++++++++++++---- plugins/catalog-node/api-report-alpha.md | 8 +++++- plugins/catalog-node/src/extensions.ts | 17 +++++++++--- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 6171ebbc1c..ac869cdc2b 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -38,6 +38,7 @@ import { } from '@backstage/plugin-catalog-node'; import { merge } from 'lodash'; import { Permission } from '@backstage/plugin-permission-common'; +import { ForwardedError } from '@backstage/errors'; class CatalogProcessingExtensionPointImpl implements CatalogProcessingExtensionPoint @@ -164,14 +165,24 @@ export const catalogPlugin = createBackendPlugin({ processingExtensions, ); - let locationAnalyzer: LocationAnalyzer | undefined = undefined; + let locationAnalyzerFactory: + | ((options: { + scmLocationAnalyzers: ScmLocationAnalyzer[]; + }) => Promise<{ locationAnalyzer: LocationAnalyzer }>) + | undefined = undefined; const scmLocationAnalyzers = new Array(); env.registerExtensionPoint(catalogAnalysisExtensionPoint, { - setLocationAnalyzer(analyzer: LocationAnalyzer) { - if (locationAnalyzer) { + setLocationAnalyzer(analyzerOrFactory) { + if (locationAnalyzerFactory) { throw new Error('LocationAnalyzer has already been set'); } - locationAnalyzer = analyzer; + if (typeof analyzerOrFactory === 'function') { + locationAnalyzerFactory = analyzerOrFactory; + } else { + locationAnalyzerFactory = async () => ({ + locationAnalyzer: analyzerOrFactory, + }); + } }, addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer) { scmLocationAnalyzers.push(analyzer); @@ -240,7 +251,12 @@ export const catalogPlugin = createBackendPlugin({ Object.entries(processingExtensions.placeholderResolvers).forEach( ([key, resolver]) => builder.setPlaceholderResolver(key, resolver), ); - if (locationAnalyzer) { + if (locationAnalyzerFactory) { + const { locationAnalyzer } = await locationAnalyzerFactory({ + scmLocationAnalyzers, + }).catch(e => { + throw new ForwardedError('Failed to create LocationAnalyzer', e); + }); builder.setLocationAnalyzer(locationAnalyzer); } else { builder.addLocationAnalyzers(...scmLocationAnalyzers); diff --git a/plugins/catalog-node/api-report-alpha.md b/plugins/catalog-node/api-report-alpha.md index cc3f7e5fb5..b61d1b1d27 100644 --- a/plugins/catalog-node/api-report-alpha.md +++ b/plugins/catalog-node/api-report-alpha.md @@ -22,7 +22,13 @@ import { Validators } from '@backstage/catalog-model'; // @alpha (undocumented) export interface CatalogAnalysisExtensionPoint { addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; - setLocationAnalyzer(analyzer: LocationAnalyzer): void; + setLocationAnalyzer( + analyzerOrFactory: + | LocationAnalyzer + | ((options: { scmLocationAnalyzers: ScmLocationAnalyzer[] }) => Promise<{ + locationAnalyzer: LocationAnalyzer; + }>), + ): void; } // @alpha (undocumented) diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 56478ec1db..7c1fe6c30d 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -81,10 +81,21 @@ export const catalogProcessingExtensionPoint = */ export interface CatalogAnalysisExtensionPoint { /** - * Replaces the entire location analyzer with a new one. This will cause any - * SCM analyzers added through `addScmLocationAnalyzer` to be ignored. + * Replaces the entire location analyzer with a new one. + * + * @remarks + * + * By providing a factory function you can access all the SCM analyzers that + * have been added through `addScmLocationAnalyzer`. If you provide a + * `LocationAnalyzer` directly, the SCM analyzers will be ignored. */ - setLocationAnalyzer(analyzer: LocationAnalyzer): void; + setLocationAnalyzer( + analyzerOrFactory: + | LocationAnalyzer + | ((options: { + scmLocationAnalyzers: ScmLocationAnalyzer[]; + }) => Promise<{ locationAnalyzer: LocationAnalyzer }>), + ): void; /** * Adds an analyzer for a specific SCM type to the default location analyzer. From 42eaf63a7525f7bce09838f78d29abfc196680ca Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Fri, 3 May 2024 12:27:52 +0300 Subject: [PATCH 196/567] feat: increase default and allow changing snackbar auto hide duration Signed-off-by: Heikki Hellgren --- .changeset/perfect-beers-explode.md | 5 +++++ plugins/notifications/api-report.md | 1 + .../NotificationsSideBarItem/NotificationsSideBarItem.tsx | 5 +++++ 3 files changed, 11 insertions(+) create mode 100644 .changeset/perfect-beers-explode.md diff --git a/.changeset/perfect-beers-explode.md b/.changeset/perfect-beers-explode.md new file mode 100644 index 0000000000..3045ca7f12 --- /dev/null +++ b/.changeset/perfect-beers-explode.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Increase default and allow modifying notification snackbar auto hide duration diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 3699aa1bb0..9240bdb1c2 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -102,6 +102,7 @@ export const NotificationsSidebarItem: (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; snackbarEnabled?: boolean; + snackbarAutoHideDuration?: number | null; className?: string; icon?: IconComponent; text?: string; diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 000eabcae6..7b55c63747 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -83,6 +83,7 @@ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; snackbarEnabled?: boolean; + snackbarAutoHideDuration?: number | null; className?: string; icon?: IconComponent; text?: string; @@ -93,6 +94,7 @@ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled = false, titleCounterEnabled = true, snackbarEnabled = true, + snackbarAutoHideDuration = 10000, icon = NotificationsIcon, text = 'Notifications', ...restProps @@ -100,6 +102,7 @@ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled: false, titleCounterEnabled: true, snackbarEnabled: true, + snackbarAutoHideDuration: 10000, }; const { loading, error, value, retry } = useNotificationsApi(api => @@ -196,6 +199,7 @@ export const NotificationsSidebarItem = (props?: { variant: notification.payload.severity, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, action, + autoHideDuration: snackbarAutoHideDuration, } as OptionsWithExtraProps); } }) @@ -216,6 +220,7 @@ export const NotificationsSidebarItem = (props?: { sendWebNotification, webNotificationsEnabled, snackbarEnabled, + snackbarAutoHideDuration, notificationsApi, alertApi, getSnackbarProperties, From 762141c019ee9406f8b3a223d686d7632849cfea Mon Sep 17 00:00:00 2001 From: Jeeva Ramanathan Date: Fri, 3 May 2024 17:50:30 +0530 Subject: [PATCH 197/567] is able to be set as required Signed-off-by: Jeeva Ramanathan --- .changeset/sixty-bears-camp.md | 5 +++++ .../fields/MultiEntityPicker/MultiEntityPicker.tsx | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .changeset/sixty-bears-camp.md diff --git a/.changeset/sixty-bears-camp.md b/.changeset/sixty-bears-camp.md new file mode 100644 index 0000000000..ff6179c06f --- /dev/null +++ b/.changeset/sixty-bears-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +`MultiEntityPicker` is able to be set as required diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index d9804a8cba..8f17bf1ea6 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -173,7 +173,10 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }} variant="outlined" required={required} - InputProps={params.InputProps} + InputProps={{ + ...params.InputProps, + required: formData.length === 0 && required, + }} /> )} /> From 598e8e51dd3430702f05c0f89c33215a0c49ebf3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 3 May 2024 14:09:58 +0200 Subject: [PATCH 198/567] root: patch changesets to handle workspace ranges differently Signed-off-by: Patrik Oldsberg --- ...le-release-plan-npm-6.0.0-f7b3005037.patch | 32 +++++++++++++++++++ package.json | 1 + yarn.lock | 16 +++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch diff --git a/.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch b/.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch new file mode 100644 index 0000000000..045e983dbe --- /dev/null +++ b/.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch @@ -0,0 +1,32 @@ +diff --git a/dist/changesets-assemble-release-plan.cjs.js b/dist/changesets-assemble-release-plan.cjs.js +index ee5c0f67fabadeb112e9f238d8b144a4d125830f..9b0e1a156dd88cee35f82faf718d82a8a8f80325 100644 +--- a/dist/changesets-assemble-release-plan.cjs.js ++++ b/dist/changesets-assemble-release-plan.cjs.js +@@ -179,12 +179,23 @@ function getDependencyVersionRanges(dependentPkgJSON, dependencyRelease) { + if (!versionRange) continue; + + if (versionRange.startsWith("workspace:")) { ++ // intentionally keep other workspace ranges untouched ++ // this has to be fixed but this should only be done when adding appropriate tests ++ let workspaceRange = versionRange.replace(/^workspace:/, ""); ++ switch (workspaceRange) { ++ case "*": ++ // workspace:* actually means the current exact version, and not a wildcard similar to a reguler * range ++ workspaceRange = dependencyRelease.oldVersion; ++ break; ++ case "~": ++ case "^": ++ // Use ^oldVersion for workspace:^ or ~oldVersion for workspace:~. ++ // The version range might have changed in dependent package, but that should have its own changeset bumping that package. ++ workspaceRange += dependencyRelease.oldVersion; ++ } + dependencyVersionRanges.push({ + depType: type, +- versionRange: // intentionally keep other workspace ranges untouched +- // this has to be fixed but this should only be done when adding appropriate tests +- versionRange === "workspace:*" ? // workspace:* actually means the current exact version, and not a wildcard similar to a reguler * range +- dependencyRelease.oldVersion : versionRange.replace(/^workspace:/, "") ++ versionRange: workspaceRange, + }); + } else { + dependencyVersionRanges.push({ diff --git a/package.json b/package.json index 5d9fbef716..768448402f 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ }, "prettier": "@spotify/prettier-config", "resolutions": { + "@changesets/assemble-release-plan@^6.0.0": "patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch", "@material-ui/pickers@^3.2.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@types/react": "^18", diff --git a/yarn.lock b/yarn.lock index c07c71c8b3..dbc233dd64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7817,7 +7817,7 @@ __metadata: languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^6.0.0": +"@changesets/assemble-release-plan@npm:6.0.0": version: 6.0.0 resolution: "@changesets/assemble-release-plan@npm:6.0.0" dependencies: @@ -7831,6 +7831,20 @@ __metadata: languageName: node linkType: hard +"@changesets/assemble-release-plan@patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch::locator=root%40workspace%3A.": + version: 6.0.0 + resolution: "@changesets/assemble-release-plan@patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch::version=6.0.0&hash=43c5e4&locator=root%40workspace%3A." + dependencies: + "@babel/runtime": ^7.20.1 + "@changesets/errors": ^0.2.0 + "@changesets/get-dependents-graph": ^2.0.0 + "@changesets/types": ^6.0.0 + "@manypkg/get-packages": ^1.1.3 + semver: ^7.5.3 + checksum: b7a68e28d03379bdc2a1d7171963990e1b88d0e5efca5f5ed490c0f583d66d0ccbd4c04ebf4e5cd1c38e1346ff0e5c4c16e1b4973cdd97fabdbad0c6996fe016 + languageName: node + linkType: hard + "@changesets/changelog-git@npm:^0.2.0": version: 0.2.0 resolution: "@changesets/changelog-git@npm:0.2.0" From b50a7a48086205d65815c53f8bb622724a034cba Mon Sep 17 00:00:00 2001 From: Nitin Ramnani Date: Fri, 3 May 2024 18:17:04 +0530 Subject: [PATCH 199/567] Added stories for multiple components Signed-off-by: Nitin Ramnani --- .../src/layout/Content/Content.stories.tsx | 50 +++++++++++++++++++ .../HeaderActionMenu.stories.tsx | 45 +++++++++++++++++ .../HeaderLabel/HeaderLabel.stories.tsx | 35 +++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 packages/core-components/src/layout/Content/Content.stories.tsx create mode 100644 packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx create mode 100644 packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx diff --git a/packages/core-components/src/layout/Content/Content.stories.tsx b/packages/core-components/src/layout/Content/Content.stories.tsx new file mode 100644 index 0000000000..de1b17de0f --- /dev/null +++ b/packages/core-components/src/layout/Content/Content.stories.tsx @@ -0,0 +1,50 @@ +/* + * 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, { ReactNode } from 'react'; +import { Content } from './Content'; + +export default { + title: 'Layout/Content', + component: Content, +}; + +type Props = { + stretch?: boolean; + noPadding?: boolean; + className?: string; +}; + +export const Default = (args: Props) => ( + +
This is child of content component
+
+); + +Default.args = { + stretch: false, + noPadding: false, +}; + +export const WithNoPadding = (args: Props) => ( + +
This is child of content component
+
+); + +WithNoPadding.args = { + stretch: true, + noPadding: true, +}; diff --git a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx new file mode 100644 index 0000000000..0d185aeb11 --- /dev/null +++ b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx @@ -0,0 +1,45 @@ +/* + * 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 { HeaderActionMenu, HeaderActionMenuProps } from './HeaderActionMenu'; + +export default { + title: 'Layout/HeaderActionMenu', + component: HeaderActionMenu, +}; + +export const Default = (args: HeaderActionMenuProps) => ( + +); +Default.args = { + actionItems: [ + { + label: 'Item 1', + secondaryLabel: 'Item 1 secondary label', + disabled: false, + }, + { + label: 'Item 2', + secondaryLabel: 'Item 2 secondary label', + disabled: true, + }, + { + label: 'Item 3', + secondaryLabel: 'Item 3 secondary label', + disabled: true, + }, + ], +}; diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx new file mode 100644 index 0000000000..80baa83a4f --- /dev/null +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx @@ -0,0 +1,35 @@ +/* + * 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 { HeaderLabel } from './HeaderLabel'; + +export default { + title: 'Layout/HeaderLabel', + component: HeaderLabel, +}; + +type HeaderLabelProps = { + label: string; + value?: string; + url?: string; +}; + +export const Default = (args: HeaderLabelProps) => ; +Default.args = { + label: 'This is label', + value: 'This is value', + url: 'https://backstage.io', +}; From c27808698f689fca403056be2007df23b6c9f7ca Mon Sep 17 00:00:00 2001 From: Nitin Ramnani Date: Fri, 3 May 2024 18:32:54 +0530 Subject: [PATCH 200/567] Added stories for multiple components Signed-off-by: Nitin Ramnani --- packages/core-components/src/layout/Content/Content.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-components/src/layout/Content/Content.stories.tsx b/packages/core-components/src/layout/Content/Content.stories.tsx index de1b17de0f..f8bf055145 100644 --- a/packages/core-components/src/layout/Content/Content.stories.tsx +++ b/packages/core-components/src/layout/Content/Content.stories.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactNode } from 'react'; +import React from 'react'; import { Content } from './Content'; export default { From 686e31b9614112cdd3ff20636464659de8de9511 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 3 May 2024 15:03:06 +0200 Subject: [PATCH 201/567] Refactoring the documentation to cover first the new backend system and at the end the legacy Signed-off-by: cmoulliard --- .../software-templates/writing-templates.md | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 766a58d01b..f64925068a 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -744,51 +744,31 @@ The `projectSlug` filter generates a project slug from a repository URL ## Custom Filters Whenever it is needed to extend the built-in filters with yours `${{ parameters.name | my-filter1 | my-filter2 | etc }}`, then you can add them -using the property `addTemplateFilters` that you typically define using the `createRouter()` function of the `Scaffolder plugin` +using the property `additionalTemplateFilters`. -```ts title="packages/backend/src/plugins/scaffolder.ts" -export default async function createPlugin({ - logger, - config, -}: PluginEnvironment): Promise { - ... - return await createRouter({ - logger, - config, - - additionalTemplateFilters: { - - } - }); -``` - -The `addTemplateFilters` property accepts a `Record` +The `additionalTemplateFilters` property accepts as type a `Record` ```ts title="plugins/scaffolder-backend/src/service/Router.ts" additionalTemplateFilters?: Record; ``` -where the first parameter is the name of the filter and the second `TemplateFilter` receives a list of `JSON value` arguments. The `templateFilter()` function must return a JsonValue (Json array, object or primitive). +where the first parameter is the name of the filter and the second receives a list of `JSON value` arguments. The `templateFilter()` function must return a JsonValue which is either a Json array, object or primitive. ```ts title="plugins/scaffolder-node/src/types.ts" export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined; ``` -From a practical coding point of view, you will translate that into the following snippet code +From a practical coding point of view, you will translate that into the following snippet code handling 2 filters: -```ts title="packages/backend/src/plugins/scaffolder.ts" +```ts" ... - return await createRouter({ - logger: env.logger, - config: env.config, - additionalTemplateFilters: { - base64: (...args: JsonValue[]) => btoa(args.join("")), - betterFilter: (...args: JsonValue[]) => { return `This is a much better string than "${args}", don't you think?` } - }, -}); +additionalTemplateFilters: { + base64: (...args: JsonValue[]) => btoa(args.join("")), + betterFilter: (...args: JsonValue[]) => { return `This is a much better string than "${args}", don't you think?` } +} ``` -And within your template, you will be able to use the filters like this +And within your template, you will be able to use the filters using a parameter and the filter passed using the pipe symbol ```yaml apiVersion: scaffolder.backstage.io/v1beta3 @@ -815,13 +795,11 @@ spec: message: ${{ parameters.userName | betterFilter | base64 }} ``` -### Register Custom Filters with the New Backend System - -To register the custom filters using the new Backend System, you will have to create a [backend module](../../backend-system/architecture/06-modules.md) calling following extension point: `scaffolderTemplatingExtensionPoint`. +Next, you will have to register the property `addTemplateFilters` using the `scaffolderTemplatingExtensionPoint` of a new `BackendModule` [created](../../backend-system/architecture/06-modules.md). Here is a very simplified example of how to do that: -```ts title="packages/backend/src/index.ts" +```ts title="packages/backend-next/src/index.ts" /* highlight-add-start */ import { scaffolderTemplatingExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; import { createBackendModule } from '@backstage/backend-plugin-api'; @@ -855,3 +833,21 @@ backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); /* highlight-add-next-line */ backend.add(scaffolderModuleCustomFilters()); ``` + +If you still use the legacy backend system, then you will use the `createRouter()` function of the `Scaffolder plugin` + +```ts title="packages/backend/src/plugins/scaffolder.ts" +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment): Promise { + ... + return await createRouter({ + logger, + config, + + additionalTemplateFilters: { + + } + }); +``` From ece858b60957a8372aa89eab2abe12e46f541bb1 Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 3 May 2024 14:20:46 +0100 Subject: [PATCH 202/567] fix: Add config.d.ts for auth-backend-module-github-provider Signed-off-by: Jack Palmer --- .../config.d.ts | 34 +++++++++++++++++++ .../package.json | 30 ++++++++-------- 2 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 plugins/auth-backend-module-github-provider/config.d.ts diff --git a/plugins/auth-backend-module-github-provider/config.d.ts b/plugins/auth-backend-module-github-provider/config.d.ts new file mode 100644 index 0000000000..c8af7eb5db --- /dev/null +++ b/plugins/auth-backend-module-github-provider/config.d.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + github?: { + [authEnv: string]: { + clientId: string; + /** + * @visibility secret + */ + clientSecret: string; + callbackUrl?: string; + enterpriseInstanceUrl?: string; + }; + }; + }; + }; +} diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 65217f0eeb..0e8db4d1a5 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,10 +1,10 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "description": "The github-provider backend module for the auth plugin.", "version": "0.1.15-next.1", - "main": "src/index.ts", - "types": "src/index.ts", - "license": "Apache-2.0", + "description": "The github-provider backend module for the auth plugin.", + "backstage": { + "role": "backend-plugin-module" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", @@ -15,17 +15,21 @@ "url": "https://github.com/backstage/backstage", "directory": "plugins/auth-backend-module-github-provider" }, - "backstage": { - "role": "backend-plugin-module" - }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "config.d.ts" + ], "scripts": { - "start": "backstage-cli package start", "build": "backstage-cli package build", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", @@ -39,7 +43,5 @@ "@backstage/plugin-auth-backend": "workspace:^", "supertest": "^6.3.3" }, - "files": [ - "dist" - ] + "configSchema": "config.d.ts" } From e4fb486a3edc86617eaccd95f601323e0b983d0a Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 3 May 2024 14:23:11 +0100 Subject: [PATCH 203/567] chore: add changeset Signed-off-by: Jack Palmer --- .changeset/blue-balloons-draw.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/blue-balloons-draw.md diff --git a/.changeset/blue-balloons-draw.md b/.changeset/blue-balloons-draw.md new file mode 100644 index 0000000000..8208af5105 --- /dev/null +++ b/.changeset/blue-balloons-draw.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-github-provider': patch +--- + +fix: Add missing config.d.ts for auth-backend-module-github-provider From 8f6a945b0ee0b33bc32dbce0bccd5e53c68ef0d6 Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 3 May 2024 14:24:23 +0100 Subject: [PATCH 204/567] fix: Typo in copyright Signed-off-by: Jack Palmer --- plugins/auth-backend-module-github-provider/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend-module-github-provider/config.d.ts b/plugins/auth-backend-module-github-provider/config.d.ts index c8af7eb5db..e79711310d 100644 --- a/plugins/auth-backend-module-github-provider/config.d.ts +++ b/plugins/auth-backend-module-github-provider/config.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. From 84c87637f3b98e3f0679df780e47c92f56c59dd5 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Fri, 3 May 2024 10:39:47 -0400 Subject: [PATCH 205/567] chore(scaffolder-backend): revert addition of internal type Signed-off-by: Frank Kong --- plugins/scaffolder-backend/api-report.md | 9 +++------ plugins/scaffolder-backend/src/service/router.ts | 16 +++++----------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index e6738497fe..6af1ac04a6 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -479,7 +479,9 @@ export interface RouterOptions { // (undocumented) logger: Logger; // (undocumented) - permissionRules?: Array; + permissionRules?: Array< + TemplatePermissionRuleInput | ActionPermissionRuleInput + >; // (undocumented) permissions?: PermissionsService; // (undocumented) @@ -498,11 +500,6 @@ export type RunCommandOptions = ExecuteShellCommandOptions; // @public @deprecated export const ScaffolderEntitiesProcessor: typeof ScaffolderEntitiesProcessor_2; -// @public (undocumented) -export type ScaffolderPermissionRuleInput = - | TemplatePermissionRuleInput - | ActionPermissionRuleInput; - // @public @deprecated export type SerializedTask = SerializedTask_2; diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index ff1796e71f..759c5ca8d7 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -104,14 +104,6 @@ import { import { InternalTaskSecrets } from '../scaffolder/tasks/types'; import { checkPermission } from '../util/checkPermissions'; -/** - * - * @public - */ -export type ScaffolderPermissionRuleInput = - | TemplatePermissionRuleInput - | ActionPermissionRuleInput; - /** * * @public @@ -125,7 +117,7 @@ export type TemplatePermissionRuleInput< TParams >; function isTemplatePermissionRuleInput( - permissionRule: ScaffolderPermissionRuleInput, + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, ): permissionRule is TemplatePermissionRuleInput { return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_TEMPLATE; } @@ -143,7 +135,7 @@ export type ActionPermissionRuleInput< TParams >; function isActionPermissionRuleInput( - permissionRule: ScaffolderPermissionRuleInput, + permissionRule: TemplatePermissionRuleInput | ActionPermissionRuleInput, ): permissionRule is ActionPermissionRuleInput { return permissionRule.resourceType === RESOURCE_TYPE_SCAFFOLDER_ACTION; } @@ -176,7 +168,9 @@ export interface RouterOptions { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionsService; - permissionRules?: Array; + permissionRules?: Array< + TemplatePermissionRuleInput | ActionPermissionRuleInput + >; auth?: AuthService; httpAuth?: HttpAuthService; identity?: IdentityApi; From 4d15444275f26bedf4037bad5c93f73c7aa48cd5 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 3 May 2024 16:47:25 +0200 Subject: [PATCH 206/567] cli: fix repo fix workspace path on windows Signed-off-by: Vincenzo Scamporlino --- packages/cli/src/commands/repo/fix.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/repo/fix.ts b/packages/cli/src/commands/repo/fix.ts index b975203a73..049f5a8416 100644 --- a/packages/cli/src/commands/repo/fix.ts +++ b/packages/cli/src/commands/repo/fix.ts @@ -22,11 +22,7 @@ import { } from '@backstage/cli-node'; import { OptionValues } from 'commander'; import fs from 'fs-extra'; -import { - resolve as resolvePath, - join as joinPath, - relative as relativePath, -} from 'path'; +import { resolve as resolvePath, posix, relative as relativePath } from 'path'; import { paths } from '../../lib/paths'; /** @@ -205,7 +201,7 @@ export function createRepositoryFieldFixer() { const rootDir = rootRepoField.directory || ''; return (pkg: FixablePackage) => { - const expectedPath = joinPath( + const expectedPath = posix.join( rootDir, relativePath(paths.targetRoot, pkg.dir), ); From c52052bdceda72743e2d0305d1f6aff3c2d6728c Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 3 May 2024 15:49:28 +0100 Subject: [PATCH 207/567] fox: Add config.d.ts for aws-alb and rename iss to issuer Signed-off-by: Jack Palmer --- .../config.d.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 plugins/auth-backend-module-aws-alb-provider/config.d.ts diff --git a/plugins/auth-backend-module-aws-alb-provider/config.d.ts b/plugins/auth-backend-module-aws-alb-provider/config.d.ts new file mode 100644 index 0000000000..1978e5a4df --- /dev/null +++ b/plugins/auth-backend-module-aws-alb-provider/config.d.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + awsalb?: { + issuer?: string; + region: string; + }; + }; + }; +} From 9f974a05da653abd5a24ea43f7f496e43291066b Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 3 May 2024 15:49:53 +0100 Subject: [PATCH 208/567] fix: Tidy auth-backend config.d.ts Signed-off-by: Jack Palmer --- plugins/auth-backend/config.d.ts | 42 -------------------------------- 1 file changed, 42 deletions(-) diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index f0ceaa4224..a7ea18ed1d 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -89,29 +89,6 @@ export interface Config { * @additionalProperties true */ providers?: { - /** @visibility frontend */ - google?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - callbackUrl?: string; - }; - }; - /** @visibility frontend */ - github?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - callbackUrl?: string; - enterpriseInstanceUrl?: string; - }; - }; /** @visibility frontend */ saml?: { entryPoint: string; @@ -137,20 +114,6 @@ export interface Config { acceptedClockSkewMs?: number; }; /** @visibility frontend */ - oauth2?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - authorizationUrl: string; - tokenUrl: string; - scope?: string; - disableRefresh?: boolean; - }; - }; - /** @visibility frontend */ auth0?: { [authEnv: string]: { clientId: string; @@ -177,11 +140,6 @@ export interface Config { callbackUrl?: string; }; }; - /** @visibility frontend */ - awsalb?: { - iss?: string; - region: string; - }; /** * The backstage token expiration. */ From ed4cd84b10ae123882b0cf549ea9f541ae7d843d Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 3 May 2024 15:51:05 +0100 Subject: [PATCH 209/567] chore: Update changelog Signed-off-by: Jack Palmer --- .changeset/blue-balloons-draw.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/blue-balloons-draw.md b/.changeset/blue-balloons-draw.md index 8208af5105..d17760a92b 100644 --- a/.changeset/blue-balloons-draw.md +++ b/.changeset/blue-balloons-draw.md @@ -3,3 +3,5 @@ --- fix: Add missing config.d.ts for auth-backend-module-github-provider +fix: Add missing config.d.ts for auth-backend-module-aws-alb-provider +fix: Remove duplicate provider config from auth-backend From cc3c51833bf48731751d435b21a341964deb2ad4 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 3 May 2024 16:54:18 +0200 Subject: [PATCH 210/567] cli: repo fix changeset Signed-off-by: Vincenzo Scamporlino --- .changeset/slimy-kids-behave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slimy-kids-behave.md diff --git a/.changeset/slimy-kids-behave.md b/.changeset/slimy-kids-behave.md new file mode 100644 index 0000000000..5e635878ff --- /dev/null +++ b/.changeset/slimy-kids-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows From 4a0577e0ea14c7b490e7ef0d0a459a8b375e292b Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 3 May 2024 15:54:41 +0100 Subject: [PATCH 211/567] chore: Fix changelog Signed-off-by: Jack Palmer --- .changeset/blue-balloons-draw.md | 7 ------- .changeset/little-rockets-live.md | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 .changeset/blue-balloons-draw.md create mode 100644 .changeset/little-rockets-live.md diff --git a/.changeset/blue-balloons-draw.md b/.changeset/blue-balloons-draw.md deleted file mode 100644 index d17760a92b..0000000000 --- a/.changeset/blue-balloons-draw.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-github-provider': patch ---- - -fix: Add missing config.d.ts for auth-backend-module-github-provider -fix: Add missing config.d.ts for auth-backend-module-aws-alb-provider -fix: Remove duplicate provider config from auth-backend diff --git a/.changeset/little-rockets-live.md b/.changeset/little-rockets-live.md new file mode 100644 index 0000000000..767fda917e --- /dev/null +++ b/.changeset/little-rockets-live.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/plugin-auth-backend-module-github-provider': patch +'@backstage/plugin-auth-backend': patch +--- + +fix: Move config declarations to appropriate auth backend modules From a8ea3c5918f8b7c5e54cad5f11aaadf11c856079 Mon Sep 17 00:00:00 2001 From: Jeeva Ramanathan Date: Fri, 3 May 2024 20:34:59 +0530 Subject: [PATCH 212/567] Check pipeline Signed-off-by: Jeeva Ramanathan From b46276c7bcdde043f0e662b66771670d3df0f461 Mon Sep 17 00:00:00 2001 From: Mihai Tabara Date: Fri, 3 May 2024 16:23:14 +0100 Subject: [PATCH 213/567] Change ownership for Catalog and Permission framework Signed-off-by: Mihai Tabara --- OWNERS.md | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/OWNERS.md b/OWNERS.md index d8967ca873..8385813411 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -22,14 +22,15 @@ Team: @backstage/catalog-maintainers Scope: The catalog plugin and catalog model -| Name | Organization | Team | GitHub | Discord | -| --------------- | ------------ | --------- | ----------------------------------------- | ------------------- | -| Rickard Dybeck | Spotify | Chipmunks | [alde](https://github.com/alde) | `rdybeck#8083` | -| Mike Blockley | Spotify | Chipmunks | [mikeyhc](https://github.com/mikeyhc) | `mikey-spot#5363` | -| Elon Jefferson | Spotify | Chipmunks | [Edje-C](https://github.com/Edje-C) | `elon-spotty#6086 ` | -| Nurit Izrailov | Spotify | Chipmunks | [nuritizra](https://github.com/nuritizra) | - | -| Hunter Dougless | Spotify | Chipmunks | [hntrdglss](https://github.com/hntrdglss) | `hntrdglss#1849` | -| Seve Kim | Spotify | Chipmunks | [sevedkim](https://github.com/sevedkim) | `seve#9951` | +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | --------------- | ----------------------------------------------- | ----------------| +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | ### Discoverability @@ -73,14 +74,15 @@ Team: @backstage/permission-maintainers Scope: The Permission Framework and plugins integrating with the permission framework -| Name | Organization | Team | GitHub | Discord | -| -------------------- | ------------ | --------------- | ------------------------------------------ | ------------- | -| Ainhoa Larumbe | Spotify | Imaginary Goats | [ainhoaL](http://github.com/ainhoaL) | ainhoa#8085 | -| Eric Peterson | Spotify | Imaginary Goats | [iamEAP](http://github.com/iamEAP) | iamEAP#3058 | -| Harry Hogg | Spotify | Imaginary Goats | [HHogg](http://github.com/HHogg) | simplex#3451 | -| Joon Park | Spotify | Imaginary Goats | [Joonpark13](http://github.com/Joonpark13) | Sixpool#5060 | -| Mike Lewis | Spotify | Imaginary Goats | [mtlewis](http://github.com/mtlewis) | mtlewis#3658 | -| Vincenzo Scamporlino | Spotify | Imaginary Goats | [vinzscam](http://github.com/vinzscam) | vinzscam#6944 | +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | --------------- | ----------------------------------------------- | ----------------| +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | ### TechDocs From 073b2ebf638851d28dc8f1434d0b36f94eebfaf6 Mon Sep 17 00:00:00 2001 From: Beth Griggs Date: Fri, 3 May 2024 15:21:32 +0100 Subject: [PATCH 214/567] chore(test): increase test coverage of WinstonLogger Signed-off-by: Beth Griggs --- .../src/logging/WinstonLogger.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index c7173b1cbe..bec6a9fc0f 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -22,6 +22,17 @@ function msg(info: TransformableInfo): TransformableInfo { } describe('WinstonLogger', () => { + it('creates a winston logger instance with default options', () => { + const logger = WinstonLogger.create({}); + expect(logger).toBeInstanceOf(WinstonLogger); + }); + + it('creates a child logger', () => { + const logger = WinstonLogger.create({}); + const childLogger = logger.child({ plugin: 'test-plugin' }); + expect(childLogger).toBeInstanceOf(WinstonLogger); + }); + it('redacter should redact and escape regex', () => { const redacter = WinstonLogger.redacter(); const log = { @@ -47,4 +58,24 @@ describe('WinstonLogger', () => { }), ); }); + + it('redacter should redact nested object', () => { + const redacter = WinstonLogger.redacter(); + const log = { + level: 'error', + message: { + nested: 'hello (world) from nested object', + }, + }; + + redacter.add(['hello']); + expect(redacter.format.transform(msg(log))).toEqual( + msg({ + ...log, + message: { + nested: '[REDACTED] (world) from nested', + }, + }), + ); + }); }); From 0cda20fa31a4a92ad69a3a3e87d93d4ee64bb0df Mon Sep 17 00:00:00 2001 From: Bethany Griggs Date: Fri, 3 May 2024 18:08:37 +0100 Subject: [PATCH 215/567] fixup! typo Signed-off-by: Bethany Griggs --- packages/backend-app-api/src/logging/WinstonLogger.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index bec6a9fc0f..d025719d9a 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -73,7 +73,7 @@ describe('WinstonLogger', () => { msg({ ...log, message: { - nested: '[REDACTED] (world) from nested', + nested: '[REDACTED] (world) from nested object', }, }), ); From 52ab24122a7644ad91103ddae7ae034a0caa38c7 Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Fri, 3 May 2024 20:27:36 -0300 Subject: [PATCH 216/567] chore(plugins/scaffolder-backend-module-githu): adding support to override default author commit for github PRs Signed-off-by: Matheus Castiglioni --- .changeset/nasty-papayas-heal.md | 5 ++ .../githubPullRequest.examples.test.ts | 86 ++++++++++++++++++- .../src/actions/githubPullRequest.examples.ts | 21 +++++ .../src/actions/githubPullRequest.test.ts | 86 +++++++++++++++++++ .../src/actions/githubPullRequest.ts | 21 +++++ 5 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 .changeset/nasty-papayas-heal.md diff --git a/.changeset/nasty-papayas-heal.md b/.changeset/nasty-papayas-heal.md new file mode 100644 index 0000000000..cfeacb3238 --- /dev/null +++ b/.changeset/nasty-papayas-heal.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +Adding support to change the default commit author for pull-request github action" diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts index dd07c88a70..e881ae3192 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts @@ -134,6 +134,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -174,6 +178,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -213,6 +221,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -252,6 +264,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -298,6 +314,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -338,6 +358,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -378,6 +402,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -424,6 +452,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -470,6 +502,54 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, + }, + ], + }); + + expect(fakeClient.rest.pulls.requestReviewers).not.toHaveBeenCalled(); + expect(mockContext.output).toHaveBeenCalledTimes(3); + expect(mockContext.output).toHaveBeenCalledWith('targetBranchName', 'main'); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://github.com/myorg/myrepo/pull/123', + ); + expect(mockContext.output).toHaveBeenCalledWith('pullRequestNumber', 123); + }); + + it('Create a pull request with a git author', async () => { + const input = yaml.parse(examples[9].example).steps[0].input; + + await action.handler({ + ...mockContext, + workspacePath, + input, + }); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Create my new app', + body: 'This PR is really good', + head: 'new-app', + draft: undefined, + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'foo@bar.example', + name: 'Foo Bar', + }, }, ], }); @@ -491,7 +571,7 @@ describe('publish:github:pull-request examples', () => { irrelevant: { 'bar.txt': 'Nothing to see here' }, }, }); - const input = yaml.parse(examples[9].example).steps[0].input; + const input = yaml.parse(examples[10].example).steps[0].input; await action.handler({ ...mockContext, @@ -517,6 +597,10 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, + author: { + email: 'foo@bar.example', + name: 'Foo Bar', + }, }, ], }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts index d6138b262b..f3042a7e15 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts @@ -178,6 +178,25 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a pull request with a git author', + example: yaml.stringify({ + steps: [ + { + action: 'publish:github:pull-request', + name: 'Create a pull reuqest', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + branchName: 'new-app', + title: 'Create my new app', + description: 'This PR is really good', + gitAuthorName: 'Foo Bar', + gitAuthorEmail: 'foo@bar.example', + }, + }, + ], + }), + }, { description: 'Create a pull request with all parameters', example: yaml.stringify({ @@ -198,6 +217,8 @@ export const examples: TemplateExample[] = [ reviewers: ['foobar'], teamReviewers: ['team-foo'], commitMessage: 'Commit for foo changes', + gitAuthorName: 'Foo Bar', + gitAuthorEmail: 'foo@bar.example', }, }, ], diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index c9d1498187..a38b070bdf 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -155,6 +155,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -212,6 +216,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -271,6 +279,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -322,6 +334,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -449,6 +465,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '120000', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -498,6 +518,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100755', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -557,6 +581,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100755', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -612,6 +640,10 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], }); @@ -657,10 +689,64 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, + author: { + email: 'scaffolder@backstage.io', + name: 'Scaffolder', + }, }, ], forceFork: true, }); }); }); + + describe('with author', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + gitAuthorEmail: 'foo@bar.example', + gitAuthorName: 'Foo Bar', + }; + + mockDir.setContent({ + [workspacePath]: { 'file.txt': 'Hello there!' }, + }); + + ctx = createMockActionContext({ input, workspacePath }); + }); + + it('creates a pull request', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'foo@bar.example', + name: 'Foo Bar', + }, + }, + ], + }); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index b4aa03b602..0a256d36d5 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -136,6 +136,8 @@ export const createPublishGithubPullRequestAction = ( commitMessage?: string; update?: boolean; forceFork?: boolean; + gitAuthorName?: string; + gitAuthorEmail?: string; }>({ id: 'publish:github:pull-request', examples, @@ -223,6 +225,18 @@ export const createPublishGithubPullRequestAction = ( title: 'Force Fork', description: 'Create pull request from a fork', }, + gitAuthorName: { + type: 'string', + title: 'Default Author Name', + description: + "Sets the default author name for the commit. The default value is 'Scaffolder'", + }, + gitAuthorEmail: { + type: 'string', + title: 'Default Author Email', + description: + "Sets the default author email for the commit. The default value is 'scaffolder@backstage.io'", + }, }, }, output: { @@ -262,6 +276,8 @@ export const createPublishGithubPullRequestAction = ( commitMessage, update, forceFork, + gitAuthorEmail = 'scaffolder@backstage.io', + gitAuthorName = 'Scaffolder', } = ctx.input; const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); @@ -328,6 +344,10 @@ export const createPublishGithubPullRequestAction = ( { files, commit: commitMessage ?? title, + author: { + name: gitAuthorName, + email: gitAuthorEmail, + }, }, ], body: description, @@ -336,6 +356,7 @@ export const createPublishGithubPullRequestAction = ( update, forceFork, }; + if (targetBranchName) { createOptions.base = targetBranchName; } From 0be2c4d808cce132cf23cb4b0fb1ab23d9de75d6 Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Fri, 3 May 2024 20:46:41 -0300 Subject: [PATCH 217/567] fix(plugins/scaffolder-backend-module-githu): generating api report for changed plugin Signed-off-by: Matheus Castiglioni --- plugins/scaffolder-backend-module-github/api-report.md | 2 ++ plugins/scaffolder-backend/api-report.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index 0284c362eb..07f5146901 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -389,6 +389,8 @@ export const createPublishGithubPullRequestAction: ( commitMessage?: string | undefined; update?: boolean | undefined; forceFork?: boolean | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 6af1ac04a6..e02d6f09a9 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -285,6 +285,8 @@ export const createPublishGithubPullRequestAction: ( commitMessage?: string | undefined; update?: boolean | undefined; forceFork?: boolean | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; }, JsonObject >; From a1f3cf28646d84e1ba24f31292d55e6ac11e7384 Mon Sep 17 00:00:00 2001 From: Stephon Parker Date: Sat, 4 May 2024 10:12:37 -0400 Subject: [PATCH 218/567] updating the devtools plugin backend readme to add an unlisted property that will cause the build to fail Signed-off-by: Stephon Parker --- plugins/devtools-backend/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/devtools-backend/README.md b/plugins/devtools-backend/README.md index 4439c8cd9f..bf0eb64a98 100644 --- a/plugins/devtools-backend/README.md +++ b/plugins/devtools-backend/README.md @@ -28,6 +28,7 @@ Here's how to get the DevTools Backend up and running: logger: env.logger, config: env.config, permissions: env.permissions, + discovery: env.discovery, }); } ``` From b741e21fa704ff78675641ebc506da6717164fb1 Mon Sep 17 00:00:00 2001 From: Tharun Paul <55498156+paul-tharun@users.noreply.github.com> Date: Sat, 4 May 2024 23:08:10 +0530 Subject: [PATCH 219/567] Fix hyperlink to what-is-a-plugin link Signed-off-by: Tharun Paul <55498156+paul-tharun@users.noreply.github.com> --- docs/faq/product.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq/product.md b/docs/faq/product.md index 79465cf53d..edbb1ffb4d 100644 --- a/docs/faq/product.md +++ b/docs/faq/product.md @@ -37,7 +37,7 @@ more, read our blog post, Yes, we've already started releasing open source versions of some of the plugins we use here, and we'll continue to do so. -[Plugins](#what-is-a-plugin-in-backstage) are the building blocks of +[Plugins](technical.md#what-is-a-plugin-in-backstage) are the building blocks of functionality in Backstage. We have over 120 plugins inside Spotify — many of those are specialized for our use, so will remain internal and proprietary to us. But we estimate that about a third of our existing plugins make good open From 036feca470fa144919d6c1ebfc9751d18e9425ce Mon Sep 17 00:00:00 2001 From: Stephon Parker Date: Sat, 4 May 2024 19:01:06 -0400 Subject: [PATCH 220/567] adding changeset to final pr state Signed-off-by: Stephon Parker --- .changeset/grumpy-toes-tap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/grumpy-toes-tap.md diff --git a/.changeset/grumpy-toes-tap.md b/.changeset/grumpy-toes-tap.md new file mode 100644 index 0000000000..ff3fbbf2df --- /dev/null +++ b/.changeset/grumpy-toes-tap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools-backend': patch +--- + +Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance From de6bcac9aeebd67b971144fa448d845caebad7e2 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Sat, 4 May 2024 22:29:09 -0300 Subject: [PATCH 221/567] feat: review requested changes Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .changeset/olive-rockets-drum.md | 2 -- .../MultiEntityPicker/MultiEntityPicker.tsx | 16 ++-------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/.changeset/olive-rockets-drum.md b/.changeset/olive-rockets-drum.md index b5e6c2cefe..e28ba6101a 100644 --- a/.changeset/olive-rockets-drum.md +++ b/.changeset/olive-rockets-drum.md @@ -1,7 +1,5 @@ --- -'@backstage/plugin-catalog-react': minor '@backstage/plugin-scaffolder': minor -'@backstage/plugin-catalog': minor --- `MultiEntityPicker` uses `EntityDisplayName` instead of `humanizeEntityRef` to display entity. diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index 85e08740df..fb18564456 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -82,12 +82,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { .map(ref => { if (typeof ref !== 'string') { // if ref does not exist: pass 'undefined' to trigger validation for required value - return ref - ? entityPresentationApi.forEntity(ref, { - defaultKind, - defaultNamespace, - }).snapshot.entityRef - : undefined; + return ref ? stringifyEntityRef(ref as Entity) : undefined; } if (reason === 'blur' || reason === 'create-option') { // Add in default namespace, etc. @@ -116,14 +111,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { onChange(values); }, - [ - onChange, - formData, - defaultKind, - defaultNamespace, - allowArbitraryValues, - entityPresentationApi, - ], + [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues], ); useEffect(() => { From 35dd24daceede25323023ccdf4fc815f0c90213c Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Sun, 5 May 2024 13:09:45 +0530 Subject: [PATCH 222/567] test fixed Signed-off-by: npiyush97 --- packages/core-components/package.json | 5 +---- .../src/components/EntityTable/presets.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 5a813c6175..6addbc79d3 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,10 +1,7 @@ { "name": "@backstage/core-components", - "description": "Core components used by Backstage plugins and apps", "version": "0.14.6-next.1", - "publishConfig": { - "access": "public" - }, + "description": "Core components used by Backstage plugins and apps", "backstage": { "role": "web-library" }, diff --git a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx index 82075df5e9..afe4336300 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx @@ -72,7 +72,7 @@ describe('systemEntityColumns', () => { expect(screen.getByText('my-namespace/my-system')).toBeInTheDocument(); expect(screen.getByText('my-namespace/my-domain')).toBeInTheDocument(); expect(screen.getByText('test')).toBeInTheDocument(); - expect(screen.getByText(/Some/)).toBeInTheDocument(); + expect(screen.queryAllByText(/Some/)).not.toHaveLength(0); }); }); }); @@ -126,7 +126,7 @@ describe('componentEntityColumns', () => { expect(screen.getByText('test')).toBeInTheDocument(); expect(screen.getByText('production')).toBeInTheDocument(); expect(screen.getByText('service')).toBeInTheDocument(); - expect(screen.getByText(/Some/)).toBeInTheDocument(); + expect(screen.queryAllByText(/Some/)).not.toHaveLength(0); }); }); }); From 359376aa9139b3bb5820d33f336d811976b55a80 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Sun, 5 May 2024 13:12:38 +0530 Subject: [PATCH 223/567] changeset added Signed-off-by: npiyush97 --- .changeset/sweet-spiders-rhyme.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/sweet-spiders-rhyme.md diff --git a/.changeset/sweet-spiders-rhyme.md b/.changeset/sweet-spiders-rhyme.md new file mode 100644 index 0000000000..58767cdfde --- /dev/null +++ b/.changeset/sweet-spiders-rhyme.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/core-components': patch +--- + +Removing react-text-truncate with css styles. From 78e8704d1d260b67048056f193d216450043a617 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Sun, 5 May 2024 14:09:06 +0530 Subject: [PATCH 224/567] redundant changeset Signed-off-by: npiyush97 --- .changeset/strange-doors-glow.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/strange-doors-glow.md diff --git a/.changeset/strange-doors-glow.md b/.changeset/strange-doors-glow.md deleted file mode 100644 index 71dcc375c0..0000000000 --- a/.changeset/strange-doors-glow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Removing react-text-truncate with css styles. From 318507fe3444fef20fdcce708d2df0b12d3e1297 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 5 May 2024 12:24:24 +0200 Subject: [PATCH 225/567] docs/threat-model: integrator -> operator Signed-off-by: Patrik Oldsberg --- docs/overview/threat-model.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index 442d51985c..2dd215e7e8 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -14,21 +14,21 @@ The Backstage trust model is divided into three groups with different trust leve An **internal user** is an authenticated user that generally belongs to the organization of a particular Backstage deployment. These users are trusted to the extent that they are not expected to compromise the availability of Backstage, but they are not trusted to not compromise data confidentiality or integrity. -An **integrator** is a user responsible for configuring and maintaining an instance of Backstage. Integrators are fully trusted, since they operate the system and database and therefore have root access to the host system. Additional measures can be taken by adopters of Backstage in order to restrict or observe the access of this group, but that falls outside of the current scope of Backstage. +An **operator** is a user responsible for configuring and maintaining an instance of Backstage. Operators are fully trusted, since they operate the system and database and therefore have root access to the host system. Additional measures can be taken by adopters of Backstage in order to restrict or observe the access of this group, but that falls outside of the current scope of Backstage. Another group of de facto integrators is internal and external code contributors. When installing Backstage plugins you should vet them just like any other package from an external source. While it’s possible to limit the impact of for example a supply chain attack by splitting the deployment into separate services with different plugins, the Backstage project itself does not aim to prevent these kinds of attacks or in any other way sandbox or limit the access of plugins. An **external user** is a user that does not belong to the other two groups, for example a malicious actor outside of the organization. The security model of Backstage currently assumes that this group does not have any direct access to Backstage, and it is the responsibility of each adopter of Backstage to make sure this is the case. -## Integrator Responsibilities +## Operator Responsibilities -As an integrator of Backstage you yourself are responsible for protecting your Backstage installation from external and unauthorized access. The sign-in system in Backstage does not exist to limit access, only to inform the system of the identity of the user. There are some plugins that have more fine-grained access control through the permissions system, but the primary purpose of that system is to restrict access to resources for internal users rather than Backstage as a whole. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWS’s ALB, GCP’s IAP, or Cloudflare Access. +As an operator of Backstage you yourself are responsible for protecting your Backstage installation from external and unauthorized access. The sign-in system in Backstage does not exist to limit access, only to inform the system of the identity of the user. There are some plugins that have more fine-grained access control through the permissions system, but the primary purpose of that system is to restrict access to resources for internal users rather than Backstage as a whole. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWS’s ALB, GCP’s IAP, or Cloudflare Access. Other responsibilities include protecting the integrity of configuration files as it may otherwise be possible to introduce vulnerable configurations, as well as the confidentiality of configured secrets related to Backstage as these typically include authentication details to third party systems. -The integrator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source. +The operator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source. -The integrator is also responsible for maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible. +The operator is also responsible for maintaining the resolved NPM dependencies of their Backstage project. This involves ensuring that `yarn.lock` receives updated versions of packages that have vulnerabilities, when those fixed versions are in range of what the Backstage packages request in their respective `package.json` files. This is commonly done by employing automated tooling such as [Dependabot](https://dependabot.com/), [Snyk](https://snyk.io/), and/or [Renovate](https://docs.renovatebot.com/) on your own repository. When fixed versions exist that are _not_ in range of what Backstage packages request, or when larger operations such as switching out an entire dependency for another one is required, maintainers collaborate with contributors to try to address those dependency declarations in the main project as soon as possible. ## Common Backend Configuration @@ -44,7 +44,7 @@ Note that the `UrlReader` system operates with a service context and is not inte Backstage provides authentication of users through the `auth` plugin, which primarily acts as an authorization server for different OAuth 2.0 provider integrations. These integrations can both serve the purpose of signing users into Backstage, as well as providing delegated access to external resources, and are all subject to the common concerns of implementing secure OAuth 2.0 authorization servers. All auth provider integrations are disabled by default, and need to be enabled through configuration in order to be used. For each Backstage installation it is recommended to only enable the minimal set of providers that are in use by that instance. -It is not within scope of the `auth` backend to protect against unauthorized access, that is something that needs to be handled at a deployment level. See the [Integrator Responsibilities](#integrator-responsibilities) section for more information. +It is not within scope of the `auth` backend to protect against unauthorized access, that is something that needs to be handled at a deployment level. See the [Operator Responsibilities](#operator-responsibilities) section for more information. In order to use an auth provider to sign in users into Backstage, it needs to be configured with an [Identity resolver](https://backstage.io/docs/auth/identity-resolver), which is a custom callback implemented in code. The identity resolver is a sensitive part of configuring Backstage and it is important that it always resolves user identities correctly, based on information provided by the authentication provider. There are a number of built-in identity resolvers that can simplify configuration, and it is important that these all resolve users in a secure way, regardless of how they are used. @@ -58,7 +58,7 @@ Backstage also supports authentication through a proxy where the user identity i ## Catalog -Integrators should configure [catalog rules](https://backstage.io/docs/features/software-catalog/configuration#catalog-rules) to limit the allowed entity kinds that users can define. In general it is best to restrict definition of User, Group, and Template entities so that internal users cannot register additional ones. Template entities define actions that are executed on the backend hosts, and while the goal is for these actions to be secure regardless of input, it is still a more sensitive context and it is recommended that you protect it with additional checks. It is very important to not allow registration of User and Group entities if you ingest and rely on these as organizational data in your catalog. Doing so could otherwise open up for the ability to impersonate users and confuse group membership information. You should always ingest organizational data using a statically configured catalog location or an entity provider reading from a trusted source. The entities emitted directly by an entity provider are always trusted and rules are not applied to them, but any entities produced further down the chain are still subject to the rules. +Operators should configure [catalog rules](https://backstage.io/docs/features/software-catalog/configuration#catalog-rules) to limit the allowed entity kinds that users can define. In general it is best to restrict definition of User, Group, and Template entities so that internal users cannot register additional ones. Template entities define actions that are executed on the backend hosts, and while the goal is for these actions to be secure regardless of input, it is still a more sensitive context and it is recommended that you protect it with additional checks. It is very important to not allow registration of User and Group entities if you ingest and rely on these as organizational data in your catalog. Doing so could otherwise open up for the ability to impersonate users and confuse group membership information. You should always ingest organizational data using a statically configured catalog location or an entity provider reading from a trusted source. The entities emitted directly by an entity provider are always trusted and rules are not applied to them, but any entities produced further down the chain are still subject to the rules. The Catalog does not aim to protect against resource exhaustion attacks in its default setup. If you need to prevent your internal users from being able to register large amounts of entities, then it is recommended to disable entity registration and use a different approach for discovering entities. One way to mitigate any resource exhaustion attacks is to only allow the catalog to read from trusted SCM sources that have an audit trail. Catalog currently lacks limits for entity hierarchy depth and entity size, which we hope to address in the future. @@ -68,11 +68,11 @@ By default all internal users are allowed to create and delete entities. If this By default, Scaffolding jobs execute directly on the host machine, including any actions defined in the template. Because the Scaffolder templates are considered a more sensitive area it is recommended to control access to create and update templates to trusted parties. Template execution is intended to be secure regardless of input, but we still recommend this additional layer of protection. The string templating is executed in a [node VM sandbox](https://github.com/laverdet/isolated-vm) to mitigate the possibility of remote code execution attacks. -The Scaffolder often has elevated permissions to for example create repositories in a Github organization. The integrator should therefore be cautious of Scaffolder Templates that for example delete or update existing resources as the user input is typically user defined and can therefore delete or modify resources maliciously or by mistake. +The Scaffolder often has elevated permissions to for example create repositories in a Github organization. The operator should therefore be cautious of Scaffolder Templates that for example delete or update existing resources as the user input is typically user defined and can therefore delete or modify resources maliciously or by mistake. One strategy that allows you to reduce the access that the Scaffolder service has is to rely on user credentials when executing actions. For example, a GitHub App integration could be configured with read-only permissions, with a separate user OAuth token used to create repositories. This requires that your users have access to create repositories in the first place. -The integrator should audit installed scaffolding actions just like any other plugin package. It is also important to verify that installed actions fall in line with your own security requirements, as some actions might be intended for more relaxed environments. +The operator should audit installed scaffolding actions just like any other plugin package. It is also important to verify that installed actions fall in line with your own security requirements, as some actions might be intended for more relaxed environments. By default all internal users are allowed to execute templates in the scaffolder. If this does not fit your organization's needs it is recommended to enable and configure the [permission](https://backstage.io/docs/permissions/overview) system to restrict these operations. From 2866e60532ba78fe0cd2051134c157d2201e898f Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Sun, 5 May 2024 14:29:52 +0200 Subject: [PATCH 226/567] add template editor docs Signed-off-by: Juan Pablo Garcia Ripa --- .../software-templates/context-menu.png | Bin 0 -> 91429 bytes .../template-editor-dry-run.png | Bin 0 -> 105995 bytes .../template-editor-load-dir.png | Bin 0 -> 597409 bytes .../software-templates/writing-templates.md | 36 ++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 docs/assets/software-templates/context-menu.png create mode 100644 docs/assets/software-templates/template-editor-dry-run.png create mode 100644 docs/assets/software-templates/template-editor-load-dir.png diff --git a/docs/assets/software-templates/context-menu.png b/docs/assets/software-templates/context-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..fa4f52cc48c29cbcacb372ef20bace4412930262 GIT binary patch literal 91429 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 literal 0 HcmV?d00001 diff --git a/docs/assets/software-templates/template-editor-dry-run.png b/docs/assets/software-templates/template-editor-dry-run.png new file mode 100644 index 0000000000000000000000000000000000000000..6095fb5cb0bcb2b45277cbdc1a3899e4a4fa42b2 GIT binary patch literal 105995 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­uS@C_AyQtgfQJ!?pIS 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
    3IrPsF}-w2vn|VqnOtFAR~E3CeAp`vmi~QBPRd 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%0Af(^KDXA22toL zB=}#r%+1)Z_!-UgPDDlVE;v>hnXL6@<}g6}?!s;M^;(dT3{{s(lcgovHfEcgV3>&! zlLUI6R0XFT}ZITzBRikC-3JSBy@gVjx5 zDKJkp!@!1GcCbqR@H6i_VOnTrkU;SoqZGJwOYKh za{!|x0Z7+SF@9vZ;bE{Z4}`Tg$HIx`@tQ{jc zTqYR3h$TJJWm~hYn8Hp@sZjLzC&v?fm?Ouzzf6%V0rZ*66W1R+fbV^R>4Q_= z#0V}elOZkINTrbnWcSURQ*LpMPxTD$?#pzbYXBIXCmbyl>{~S_|6-)a@pkGCEsMSbM-c0(Zj0ddkRe=PjNb8_#2TAWxlL zZ@-GP!aLce&%>CEJ=HB)nnG)i9{GTr)S@}%X-)ceTx#x}%T9l_LCQb(=KSC%{=~6; z|1-iK1D}!gKHRok(3kr=|2Y*fBP<>AHZ}_7?k}(e&?TVtYsMB z{js5;0zXId7oYm)QV;mff1_zy1^>kU|M;x`aVP(8>i>V8deML;439s~7;su+0;^Q$ z-3s=w@p5mMbZ+xwOcZ!pwrJmLq0KV0|Mz((_K4l&~ zDl(D^zV3DADiwrFKp-UtRuf{t$82s%;W>W7Px<8|uT6ewygw=;eWGdS%`dFs7EQni z3aY2kbq4)*bqfkEVR{-}#RhX+_G-*^R<{FF(o>JLAJqMJv)q1EcfOfcrP14LQxgyC z=tP0m!KuU>7w24habc&NrEmZZv>e>=+$r=73c-vOGk$+GJ9xCb>lxA5k+iaC*_pBX z9*S4qT=_D>BY20oAijQGWpCGXo%%b+=5ei@{6bnfF5G8ZL`qinh!-vq5;u#1o_2LS zR_+Be0k8(}s1sA3E#HQEL`b#|GW?OJP?y`O|6?Ei!6Nh1~ zQyXdLSn%e1EcmFRK24sS@mhN}t`zIb9stH_RUbXX;n!!tqX|P0YbYv4eAM>xZ}04w z0|T6*vX&AxC^a=IC5n2c&aJw1VhRdWD4%9lRzE|){80dEogc~ZDC)@saw8Dkr90X+ z=3P`eIb${Ux&!cuk=YJ}Suuq?68J#Z$9N8%<=xOC|85jkNjQ1BjVLTVG9iKRBF(d* zQam^~45+spru8{88j;?qS+tKAfBw8Xz-8go!O+ANMO#OQiZS!sP|2rZMRW{Dkmkt3 z2r~$Dwa+*@*@>dlc40M4{NMT07BgI62|P^40}{aBuWq5mH8Zz~x^>|!1&DP3YxVTu z%O7b8P_@wvz1!AV-r9J<-BN3SKC}y*#3r47%vWu5qFvew?<=jHeZ#T>w2Af^&BbBq z!4gGo_>RyW%;zePu*|-_Ukmh*S^;&k*O!hyaZJhQ3p!Ou7p7$A&Fh~t3BUKdlv{Q= zo7?P>e{5oX-264{{5bu(dtZtP^6IUwPaBgnv|@;#2`GC%f{b(iXZbS z&g$il|1Hg<+zBU(A&?`y+FZW65{1oPlw}3Vez^K1pjcb5mmJ00v?zj+J5b)oD_b=K z<*PZ-@o891*o?29Z;;Bwn>GJ!E%~B+=_h5RR-pStyaR3QW2h{_fNfi>)iv`QPQg63 zQ`|6io_+hH8T#85N+9|=7)BtPYWM@VbRui(4C-doWM0JIea?yzQNZoi7u;@s=Dx;}cYhCwex8*DTb&xd`E z$ew0R6r!7HoKNIdpLqfv$V<^G?P>>XqC)72_Im#gi?to~g}`%wBdOeRMI6=UDO&Llpcj8NhYcRj@JBCMvUEfD(!pmp%(O+dFFO2{g-X0d#3ZB?E% zcGfTlK=r{Px&&BfyDg>}>h=ml-IX%fWYa8wIekDtK%+HN=d6|CM!s*h&q^j0Wy&nt zo60JpJ*S1L>6u#tW{yiHS38f{(>M zx3vunjo?gHR94o0>$zYYIZk`#7Hr=i@wFBi9RoYIu#mpsV?JwcS58LjH%m?qjse{_ zaVE_|C$8s>aWB&lqfxJBfO>r=C#UIse?U(`TGuKC7oRK^3UU5Q0`=geq*OKmIVV1$ zqDtORL_YR%lTPY~>Q;Vh3%m=5sDvOE`hOwN(8UDs8cTp_q~q_H$^aM5$p!Itgrj?N z=9&0rB_~c4=D4e`t*D}7Z21uQ7ib-3>No8I-IP5ZsF$d3 z2xR5s!i#$m(2hZ$qh_ub3a<^0zP>>_yBrw1+^cby57>;iz4*dXJ^iR||7bZ0AmEh9 zzP8{VVvg{6S(<@JPjRRCLXZS=O^tfK2cUSnF3Nbi@ZApf1sO&f2^4uqOY7aM8X8VfzV|r{ zW-7t+Z(bzZG>NSzwKO(YJ9eqNUC%TlV|p&S&!w6yzCF!E@wspTY5{@vN;NCuTOJ0nx)>i$)%T-VH+z4vr{h)MwB_jgH(U01keT^AEFtK@=`sJ^3sO!3D*A$48r4ZIbZuoq#oVyKC*=-yS%@_^s zr~y0O_HI6aI_DqPF!w~x#jPg=uTg#{So&05gtQhZ=KQ0PmI8}&(l;OFJJpq zKB%w$l1X{7c`?MVG6A}ENYfiOw>_V+{8CvFI1SMyJQ5J2NFKhBb~A()H9=1KoRpm zLrgJsi=L9!%l-4ns3eOazFE~i^7l*>Pn87Ewwfyp#}5Jd=pc1XA#2_8@^Y$0{&dH6 z=1*!vKIbdn@Ub#3s;yK|^ci+x9q4Pu#>yZSrs8#^vgv8pM61G2G*o#B2A9^=QRfvt z-m@+)eCdgaR6vd9(Ihd<==!sS$s%_mpfOPVMNdnK8A!z^51+3+z{4|6pW_;EZXzg^ z0~8f(YPZxqTwVj+w=)nxJcbODePSg#O4;ahd(f$0@Vj^H&Ea23pZtn*>ZqF8qbiubDc+##{|M-Nr?CEmNZw@C@;*e zc(wK=p7X`Sc?w1xV51~J-ILC!K_^)8p$;Du^jz{y_l8Xh^_b(AwJeSG9(kN`TY(87 z!kvX>Bsrk!PA9qGoq(dHt+iumSWr;KScgQ{d|$bv11g>__MJO%vML}wogj4O3(JSv zKE}HtN2ljp^F{CyV(e?^h83De&!}wh5 zUuPoy9#@Y8j;DUDUC_>Sy@a%MaO%WO@~L^(3Uo=Dp7d`gF;qw!VX7#=qUlGtGDgeH6Siio&g7B@Xlrw$Rbbg?(b`O*#Np_OmMhq6U6sa z@=rzae`P@kAwYJ|WiyoIN1*&Xq$Q?+ovMWEvc{{=;|eJ#kC!*Q;y8xXM%h8%*m?0V zFUPc=K*?=Ft`#Wwz^rHy&{&s=s9Va-EfDiQ`g{}Uej<}DY7;s=dN~rjUly5BZX$BZ z8oo3S8Izv9zg%)>g4Fa*iHqy)DbP!o;$b~YdOORprE>#q_vZn{OT}~e$-%qVv-UV~ zrFpBnR2tnqP8m;DytTEjC^f6hB4P^TI*d+&f}S{Zw9KF*iav$QM6mA5X$`p zz}i_2upebtmrsA>rkHF0C)e8bTdws)zp?J)xEc{xD>6Pi+l7W{*t^guzk-VS4Y3X| z7Ya~5$Qp_SuzYMxb82AEt37G+-OZyp50GQv4t-$-EZ3y#9NMXsp8@@1C^@eW^zEjo zJwDxZ3k-I4v>z5OZp-=%4n+^+E6(TgOk2Ux)pzc=bM~2_nG)f3IX0Mk`jp1S&ug5| zGKH4mSbL)%`Ho+_hw5=yNVp59ix9JJWxG59DML*8s4~J~2pvnymOXZ|$~d_f=!9%d zE4q@mgzp2`<|~Lxn}GQl-f&gH@dYu+*T=^YTE6B5;eN&_Th12fZU0tXu(Z_0EBpeI zAk1fLZGOn>WT{h>YHMSCs=Y7?;iv596D}P}yw0;8jVUC}45~uqVwiEJe=C-2V~R*yDpW4Hk{^Z9Gbt2nzsOD8yTwNFNYB8UK1$8t`^_K*v8&I*w{roSycVXLmbEVn&FV=_jp8y@Y z>t6iRphS-jl5 zFMwWdy{?YB&pY<^_D8MQUfKIRPv_G2yu2&jU1Vh|ceT6tygl8Udv~03x`cohVMGEk zakSLBX=e3aiG1B|820T-J1>QYr0kKAiBxajN?xTINJ|&4uE~|VJP%TM7tgw~TiJ~^ z1rtfX!S`;poM+G2h88<$svyY67`W56uIu6ePz1T#Gm!}oCXhGNRgl}8}@Mv1TM zn@TMc+}+pN&wbggr`P>OW@eT#AhNU@f2v}W28jZKi)NP^T!c5v z`CcFYFU0vBKpOk%i@P8X@$ba+V2H5Yi+3Muw|R+K_xJYVTo&9V9&TF>x$Z<|W-04{ zr~noyrhOGRC@hQvPlGR~{L7n|BybK@m(h7YPjxz{AI$W`=oa!l!g_$)*Z*|d1MkW% zEHhM0b#j8El%iI(q1k3iJ2*H{ z)e4u2rk067Z)-lBBzAyvkVo`HgId8@N;EVyb6zt29^jTqoDYm%Eu;+$pKRyqwSe=Z zxmqT@ih9H1PX~|_EX=IXKAAW%-+d~i|Ja@787%`-EF7j!J}zuoXxUIb7IIh6`5O%t zlXU|~&-LUT39plI5;1Ykis~Zb63(?iDJ501F;V2dkeDHqeC08+Um@xJHND&dUd85h zj(I#o(W={mPYLKU?MxjpsP`@iKvom;(qrWJsz=EqsQxyx|M^CX#P_}|N_RFMydUEp zC=BpPjQVcHfWS_oMYBNpVd_-Qwo%6iY$#BMyPxP)X?~-r3we>__13fErIPnc^^ZRT zl}wm+K%kR#Cm-qh$3Q0fsb zz27#q)eX;%?IwON0vrSE1b0fW>WJ0Qvj&>Q1rEDtd}x5v3r_ep0i~HXAOzH#9vx+5 z;r~W(5gQdnUx!1Tv|qZl{;EprR5U2Df*IgFAG!f>tj){p{8#XZ1kwV+j9lWDA?=R= zWIYGEbm$vo;`OPvh_syTk{5JXjJraH+&@66KWq?H_ILM30%a$2`d5$WYt!MrGxu*I zKaYLgRFfhD+bcdI#dTWurr*|v1_Jd&jtb!&LFc?0OMK6sjW@8E-sVTaLbrrHrPvEN zcjw!^R8d_mOe<-1vX0KfO$dpk&uJ==ze_f6TjXP!+&lfyYD!&8kC}AWYuN{KK=Q4C z-FJ-)XLK@GwS>~}4w0Z02Zv~@aI4h(Nh=ZdUUXYS|tv_1q-0Uh?F_|X3YzZZ19j~SJhx_lfZ@D5zr5!Rh;0F-ljz0ssY#p* z%)CZ^o4e~*6BV(zcr!EEtGGR>2{eE#rNcpsH)LnH_10;bL9c4hpgl@PNC-&81@bE4 zdPLX@X#3e^vK=+jS@ue(CyR!rd~||#JfV2nGY@?N|2Qa8lw?^{RDfRFte0(9Lhv4W z$1{R~exS}~vA)9w(4l#pL6PArIAdGICv}aU5BK&aT$(YFtYLN zU7@=MAzv@@5{)UQu1I}BvC8Crab=edr+`{{Xsa;Ow5ol05WiT#GlCyKVc&i&Mx{S;BOZe1XPkqkH@%CnlbvWztQiICNS4L-M%y0DyRa1<+Xj;h8Gsu{3BgY<+fSIDX18Hi=W<1)!bc}&jBi~Q zm7cW_DYT_a_!lIUG64t1>|eXP_)SddZn!}0n8#O;Q`To`1{0H>D65`+5;57bwk3m=Y+uftsq3+(QOgTGef_c1s?zy@Sw$S0 zyIoQ{?P0rwuS_09chtU`{FVku8ex=TFsH)so=S%g&I5{>gD%e@MH7kagM-YtxFpgQ z8%()%7;(KVJ=N9ffv6oiq?q&+tiC90GTUnlsOOuTGDSFa?L(=$DXHD&QD|&<;gR%- z9i8@!vgnx{>}@@ZGBmfgPgSAGE^))nT@IXv4jwBxzW=EyZ1hl5>*i?|qB*mXc z1_nNDX*M8aD>-)N_VCn8dzyT)PHdY~1>=e;N;bdkMb-u=q;6U+UfwMR)kJC#$LHkc zCfy~OW(S=jPQt?NB9hUpFTsO(P@_m@)5Wtm!h!nYpEd;yJovXeRSBAoPje_iF~zqW2#6#Is8CqL1>9_s)q?9o~z4rChq4+n@p*r6c&&Ze7Fq@@}K27n3p7RT>Cnys-dH}vj_>yRY;fc1~ey; zZa2Nl&@>vJf;p|Sl+Pa5lY1X952Udx=p`78_}*e@X!P2wSF>Er%E?KjXDI3(7>E@W z6`d$PsDqCL?iU;<{MQNp?E>FJx?y5tHbDfo2g;At(L(zm|06BV3}|lc9k*FnMTw)i zrFD+qrPXXBOu!<)G#dOB8|faQpzmpz&3R9={MdzLsj|G*tqG2*kPxZ!#k7jCCq%;o zBG6ucSWM=)d3vnzWLl{@=Lj|+N{PxGtMbzLF-MiNrlpAnwCULRB)NuV^zG%tLhBu& z>8n@1zTRJt%z0_6p_3yLDX{|d!~UMN=ZuxCl-4{e7E;n)~KXJ{9nEaBz4)WN@peP0VNz&)0=xB6fUQQK;JGPFCOz zbXdn4f>z z_z}_FK?7Md?)O1TuOj}tS^U@N|M^;06O?$jj@J z6L+C!z4>qc^Y2II|Eh&zM^y2+tL1+^uD&mEHUV{t>ZXyyD!hbe=A-YhfeC*sABH9R zzZ_+Q6ZA)lUMHl{%MT;Z%E*F*osmnhr$)q^|F;W>a1CkwW`CAvE#U$yy4jHb@|{kEjZjLv2-rnF--QmeP9(q z;r`0XO8W*${846J9@iFBAit<68bA~IgNkBjbz0*iBQN8_fI}fR8DjkW#CqnY;w_4b z^2;+r3ybK*(?QN~-{-&GHETVJP1CMCaHwV8WrME!USf0DUh6BTKRwZ3n;z9nX1pTiNJ;U>(@)=t=DS&p?g^i!6|A3P@ayc{BPO?GZRwG7$irgw$* zWSXk}%S^EQO362p6Jb(9-0hWa8k_1xifpIQrH1J3%O4XkjiLus=wH-zsF@*`GHB=(hCW+D#vr~*8&7oQIwzr)DR zmd!99GgWqy-Fas_H+!3pk1sz9#NW;iT>mGp_Lmz_*W;$82u#=IV#+7{1KiTb^?3&x zw`=MUs?J8QB5F`5OboC6{P#)mMSnCrKmLLyoHXhtjNe+1`|(Av8gchbhs5pD_*i@; zog(AOlbK`uXh>k5?$u_7muf_Mv2!f1HK$xMPyFTOEjl3HIde z*$r)n37`JT-TIq~*pPx6WEAwC`K*nv&XZ?cZ< zs@KiJlKnQMt5c4eMlQ}JWbjwP*UyDJ&18@!lDyHjl1DLC5B0+-qA=ClIBuJDW5-1| z0yG%NCg1*W6~cJ=h4dPEUROx@*|mzP8$q>ip1|+$Tjzpvl#;s3_ttZxD{Od4CB!>M zC;W7cpQcgV@TEZO55~Sn;D4fbv&e-*uleE9?sLZnx0u`qnTqm~0ywJ{fK^W|`L+A( zGWd33VMRlh->jzWlK{8q(mY}?7(A9ZZvj>Ug5g(w?_EH(5&B}Uo>ED;j>XXGAK~n} zL5Mows=5=|C1X=>DyH+^*x3eS9cAY~!nWAo$@M$B-Ah}LSDv)qq^=f!D8X_4MIKvv zYjZREsGQQ+xjoHrv_P!ndQ^?XFS(^79(YBoTJ_q$woD?N=dE(&@)eqSK$X&o`KNph`Y$*Sq)J`#o|ovEtOixTm;hBgecvB7w7;ih>mz<^&+$6kZQ73>jRpplu02HpLO~;7ZFvoG} zNC%xzc_+l@xtI(SXwZ*uR}V6~0|MS#x~W?sCwJV>S1`eeXK#usaJ)9+RFB3X!&@m9 z(JgTEr{0zDbMd-fY<+$SM5+9TaWkQ+5LoWpV1rW|_Z4BmUH%D|503fGd|t+4Y#o8x zx20Wz-+cw*YV-I_F{de0bd2!qvk>)s>!gRoW?hiE@w4T$12{Z~uXvOB@fuiDId&Vc76_K>xO14_pffOjXDlpPBhFK3Gz~h?KL58Y9Zo< zuEPM$1~cgGK9(1wV=QB**55KG)nNn*uCDH*jR@OVf}6C&3G6OSn9e?y7cg8B64|L@ z?Z+EFM_K!HatD~dV&e^TF{XQyNK@Htb@#cPZOo8*&ssNzvZWf)aRxR}%kcovA&z1F z)2Mu+Us_!ikB-jkH74=Y(^G#nG6;o=O->M56jW4jO-Bli_k(I>Z-3NpvI~gHlYML& z$utFRn_kF#{M|L&!skxXlVTohY!>d9!i*5vj19o=#hvLHkWmALNU6!_(2(}!XReu3 zJku~TL)bayQY)Dj_Xi{5MPLm(#~8+wN!|5boYX41?+Di3?dsDNKE_UEA9#11cf*zx z+DI8T4<9@kTt+H_4ZSY^h5?bZ zRkx<0F`cFKs{+DY<453XG(r}}{&grxKxR+hy*6)Tv@?RLGO8$s(|K)H03QVz%pu`9}H-G33lJ~vZ z7Zc_Y3V4ujvjFV20SQ*@AFD$+9Vz8LRLe+}I!TH)t}@3j1^gX&2n1Wk7ecE8pJ=`9f&rpVHn0=wMg^+4M( zt)`Iz;ME69Y!qf#{Dyh#QrT-*UZ7`sZ4{&Cu*hJewcX`)ojj z9{JSV0Ir&uK-ED)LfVYe*^fw^TAaO3a+TX}tQ7&KO2eVk;c%#7XYpI2)I>bF$7hRm zm+0r}CfsIM(W&GkzC(yYnZ}03t*7^62FWl8m@lq3T0eJK44*eOLd=Kq zAmebJ*WR$U@S=rDr2UD`Un8GbZz6LO&PYWwS|jbSIorQR^7Q6rGfU+fQZl9eXwW>*J~%)F(-I7(tL0VUqzGJn9eF%k_%i)28d^3`W9q(@K9q!9 zd>o*oon*ez1>m?w#2`@^+3>_?r05Uey>$PLaPOCxJBX8$sGB>pyY;p8?__tTdJ5U> zr?9Puc{Mn>IL5*d3nL|d=6|&f{sRu;C=2&g3sSg{@d=pSluYFULfI3Fmf)XbON(^b zn!TVX^B${F%PrRU@}rT?hB*~$YTib_jx5P|S~U1AeXmTdG$YsTYDnFd_;A5HUI}1B zK7ZdL7zs|55v}EVi>kSR_p&GEp2PN8oq%0N<&Zsjt*Q-}2Q`HNf!sY5hW*LR5&Fz< z*z{Q;&V>b`3rB}9MmHz(|?K3K>D~o$LH?sKm z@)*{amI}$qOXUElJ~%iiZ;Ynt!^cn=YE2L*b-O`D;fA@ZQKi))09#BI5VwhVd5B7j zRQ!cAW2!@WMM+2Q8mIMc z^XV5_TNc~s!?SaQ-m0RyMZ>+`Y>^?^y6#RgTwEjT``f=1V#w0oY+sYjZxh%|T{~`% z?2)`*E4V4>Z0f}j7ELaUjN6Q_$-mfnTaFnWQGs$vwRXJMb z@+nf#j*hqMgZlI^`BZM%U2E&4H5&(QNLc+nVDMTv@XV~XK16FxRMJwti<8C50KiO8 zMY3ZM)?p@qV0o_2Iu_D>v|g?WiK*-lHJ*K`&%LX)wzelq@^N9-rr|5qxI!uC`vVsm z?`ahz@dyv^Z(`+n41v{j^o-eA`XrfeL*k>t!&$7G4)M=e-|=bUf|#;HL!Ih95BWJb zX6_!jY`%8qWgpj@YdLc#cG(B|n+FtydKg2R($kxzmGm|8OlqK0()b;DAiC*3S~S1nI)^@`GRG@x6~ClQ>f+i<^BHJXbh+`)f0y zIWJR7ir6tNzg;;m7&j1RE;=XO<^|b0dk+%Y7F7lzC~<$yaDiGzTDx(ejt%?Gk1I+Z z+j%vlGffNq{@E5Z^k*C#%3@xmZXq9AudjXab*%6EZj#jQt-us+`nFO=G^Wav3R#WX zj6R_unjVbpuC)>MCqfH-=*ysirD|iAaZg`>Nd_abb!AXK+H54|5l^S#R_yrXNHWpf zWQ!NSZb4-=Vh?N9d4Y0_mq32q#%vo)l(`gUtv@$SUEZu=u0dQeM!=Ht*xT*^Y@~`w zMNK_=LR8Ts^yoD|KfhTY6fw5%gTQ^ST74kW{$AhF!#d8D#g^sENBCrLO?-Tn#kj~{ zhU$L-a#)d&+vI0nt-$`0@%Ha|7{tr93V>&zbVq&Qu9l_vOt(l=z!RG^%(;yur ze^K%3mn&V-0q2$}hZ=mWvH<&8cmp-s$2&uIa3~l-W9y=kL_{vU;I5(7MKrTZCz#ea zGd3{isK`ekra}yMI=$Vh@lF^qn3ivm^Zu;f(5}^ovWP)W{qb{KG$be?C<_d)BOFJNFKii);)J7!H>y(L$LJP zkxKK^prYZjQbcm`q?&q+lJmMTST7L`BV3i5PN=k|Q9B47$SzVK$uV^@u9MZbLSJt! zpbT1*fO` zG|!pgyHqb_I=bxF?7CV4!Dg;@POcXA(Z&wEtjvDJda6w5r=R_cdHr`+?fVoM0{BzM zd{)IDV^D>2u=UuZn5}TDVm-uaYtW%Ow_dtHJ-rlvUC9ITR8u`~EHWg-yyc_3k$Jd+ zY}_su*$Zf{HT%2wNcOUO7w!$YSV%I~M)-5eV39#MCT-y<%GMRJ7juhW{y%S%--GkQ* zMJHmaoa{&+;d^y;TdQUuU0`-t6YyE|#pqP>`sz=V5J}qPBJC>)C-Pz5;YhnB~g$cEphwyN4d{veFzV0s^pCny_`2bbs99VvBf{W zBIq?D&$!-e*Ba&Hg=~g6U9fO3TkR_>BuOs_h-}YmIlRFZXyBAO)tMW9o-zY%AdxD~ z1`e70=xv~e1g(1(=Tws0+O4<8T&av)_3iRn%c(1h`x%0=ID0&wJukd3xp4~OC$^0 z*P|8vtntdNcCE>)t6OFh+f|kB1H2X%RwVnFKW*m!z?r{WlP&ppta4ypi@+e?pT^0q z7{wFFSD!X?Y_sz6@pjKJTDk-Kfb`oJM++;~+Z~1PJ*Wp$cty$5(s1wN$&8etAB{|F zDx{??Sd8^E5YVz;W%7o9-=~M)s{qE55|?oqk)|}{F`}_|)zfAYr2P&#oGe+PLv3KSz(wi80=Ah+Ak-JvdN9fB zW}`vv1vb1x^~Pf?_e`nMUhj!L{@MAu^ZhraG`3#0aqx!n5#s!Qt%c|)l~sTLF)T_$ zcTlSD=g*Wc^0@RKwO@SODhbceoOjK5&4Hb>w;3r`X*cqKC||(mn(|9vb;DUFC$GnP zc6IN|F_PEh1O&FD=^;gj+4f^f|3NDH{5->)Jz)DJNEQZ}JW!R04b#p^D6y8fc z#(aomDD|N+E7|*~bga@|9#<~pIO~yZYQLvoME3lbE-mC5jbZBh&j#0)I+|`l2fPG% zmBa%&If-1!@K!UJOP*B@>kCTf&6nS<8uZK*MK1V;ICXq=8R^v4KudH&_N9o%oA#~$ zwt|Vqi0{oX87-eDY3%GJ-5)p0C_l}qA-i3mB6kOtTi;b*ta0H=<@0!6uzg&Y)#!hH zl(#s-W{(=uL-}Z7Xh@-<(>?4(HP~Ch4$Q9CZXoz#@YJTMUv0k3kh?cgg{DAV_r@oy z4(Y_I1t>JNS6YUak1yoZvpiZ|zggZUZqZTkm+Semu)lKyxWnW#a3j@+gZ)N*=f1feNaxJ&8RBql1l1}lOD@=Z)l zsK^y}o3ZE-M-nycI~F$Q*x2w|Si%JI_8MShNlz{B;^EoOU_zUAoo$y+6 zU+#c1hi)6;yWXwYaOa(bl;RCL}xUiSfL&%=m$_?tJpW~J-GPyDG2nr zeFoLp$A39N%qol2T`MU2X%qk7ll3c7EdZetw7jsou_Y~&`(}9D%&;2X7SE7hSOVtX zr9U;s#NfYf=gKcEj2cW6l%S#v@hZ*;@NDp+drB8HS-QxG`1t8jI

    -#3vN&C*=JV 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 ZfgBe_mkn Date: Sun, 5 May 2024 12:20:51 -0300 Subject: [PATCH 227/567] chore: requested changes done Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../EntityListComponent/EntityListComponent.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx index 9c4af562e5..58b541795d 100644 --- a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx +++ b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx @@ -21,6 +21,7 @@ import { } from '@backstage/catalog-model'; import { useApi, useApp } from '@backstage/core-plugin-api'; import { + EntityDisplayName, EntityRefLink, entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; @@ -138,11 +139,7 @@ export const EntityListComponent = (props: EntityListComponentProps) => { ); return ( { {Icon && } } /> From 069ae443425f67b0784f7d60049d52b6467c3219 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 5 May 2024 16:09:35 -0400 Subject: [PATCH 228/567] docs(nbs): extending the model Signed-off-by: aramissennyeydd --- .../software-catalog/extending-the-model.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index b3d7de3a65..c058a12b12 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -585,6 +585,50 @@ export class FoobarEntitiesProcessor implements CatalogProcessor { } ``` +### New Backend + +You should generally create a new module to hold your new processor. You can create a new backend module using the `backstage-cli create` command and selecting `backend-module` option. To create a new module, you need a plugin ID and a module ID. We'll be using `catalog` as our plugin ID since our module is adding/updating catalog functionality. For module ID, we'll use `foobar`, but this should match the ID of whatever your plugin that's integrating with the catalog is, for example, AWS would be `aws`, Backstage Search would be `search`, etc. + +```ts title="plugins/catalog-backend-module-foobar/src/index.ts" +import { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +/* highlight-add-next-line */ +import { FoobarEntitiesProcessor } from './providers'; + +export const catalogModuleFoobarEntitiesProcessor = createBackendModule({ + pluginId: 'catalog', + moduleId: 'foobar', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + // my dependencies + }, + async init({ catalog, ...deps }) { + catalog.addProcessor( + FoobarEntitiesProcessor.fromConfig(config, { + ...deps, + }), + ); + }, + }); + }, +}); + +export default catalogModuleFoobarEntitiesProcessor; +``` + +that can then be installed to your backend as a regular module, like so, + +```ts +backend.add(import('@internal/plugin-catalog-backend-module-foobar')); +``` + +### Old Backend + Once the processor is created it can be wired up to the catalog via the `CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`: From cd24dca046e0c9ddb6be003d6e12ce82161915f2 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 5 May 2024 16:14:43 -0400 Subject: [PATCH 229/567] docs(nbs): proxying Signed-off-by: aramissennyeydd --- docs/plugins/proxying.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 40d155e13b..bff38fea40 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -15,6 +15,16 @@ can be the best choice for communicating with an API. The plugin is already added to a default Backstage project. +### New Backend + +To add it to a project, add the following line in `packages/backend/src/index.ts`: + +```ts +backend.add(import('@backstage/plugin-proxy-backend/alpha')); +``` + +### Old Backend + In `packages/backend/src/index.ts`: ```ts From 0911fa60c8d6cf2ee521fbfe22a4707c905d9951 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 5 May 2024 16:37:41 -0400 Subject: [PATCH 230/567] docs(nbs): plugin observability Signed-off-by: aramissennyeydd --- docs/plugins/observability.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index d8778f07f8..bb6f5abbe4 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -37,7 +37,39 @@ An example log line could look as follows: ## Health Checks -The example backend in the Backstage repository +### New Backend + +The new backend is moving towards health checks being plugin-based, as such there is no current plugin for providing a health check route. You can add this yourself easily though, + +```ts +import { + coreServices, + createBackendModule, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +const healthCheck = createBackendPlugin({ + pluginId: 'healthcheck', + + register(env) { + env.registerInit({ + deps: { + rootHttpRouter: coreServices.rootHttpRouter, + }, + init: async ({ rootHttpRouter }) => { + // You can adjust the route name and response as you need. + rootHttpRouter.use('/healthcheck', (req, res) => { + res.json({ status: 'ok' }); + }); + }, + }); + }, +}); +``` + +### Old Backend + +The example old backend in the Backstage repository [supplies](https://github.com/backstage/backstage/blob/bc18571b7a742863a770b2a54e785d6bbef7e184/packages/backend/src/index.ts#L99) a very basic health check endpoint on the `/healthcheck` route. You may add such a handler to your backend as well, and supply your own logic to it that fits From b17e7bea59faebfbe685e58e47b8decd3cb09367 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sun, 5 May 2024 16:52:44 -0400 Subject: [PATCH 231/567] add section about logging Signed-off-by: aramissennyeydd --- docs/plugins/observability.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index bb6f5abbe4..e13653706f 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -15,6 +15,32 @@ See how to install Datadog Events in your app ## Logging +### New Backend + +The backend supplies a central logging service, [`rootLogger`](../backend-system/core-services/root-logger.md), as well as a plugin based logger, [`logger`](../backend-system/core-services/logger.md) from `coreServices`. To add additional granularity to your logs, you can create children from the plugin based logger, using the `.child()` method and provide is with JSON data. For example, if you wanted to log items for a specific span in your plugin, you could do + +```ts +export function createRouter({ logger }) { + const router = Router(); + + router.post('/task/:taskId/queue', (req, res) => { + const { taskId } = req.params; + const taskLogger = logger.child({ task: taskId }); + taskLogger.log('Queueing this task.'); + }); + + router.get('/task/:taskId/results', (req, res) => { + const { taskId } = req.params; + const taskLogger = logger.child({ task: taskId }); + taskLogger.log('Getting the results of this task.'); + }); +} +``` + +You can also add additional metadata to all logs for your Backstage instance by overriding the `rootLogger` implementation, you can see an example in [the `logger` docs](../backend-system/core-services/logger.md#configuring-the-service). + +### Old Backend + The backend supplies a central [winston](https://github.com/winstonjs/winston) root logger that plugins are expected to use for their logging needs. In the default production setup, it emits structured JSON logs on stdout, with a field From 717d12ab321402563d488e145cad6b4cddc5d844 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 6 May 2024 00:55:38 +0200 Subject: [PATCH 232/567] docs/threat-model: update for new auth services Signed-off-by: Patrik Oldsberg --- docs/overview/threat-model.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index 2dd215e7e8..a2f4b4a1df 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -22,9 +22,16 @@ An **external user** is a user that does not belong to the other two groups, for ## Operator Responsibilities -As an operator of Backstage you yourself are responsible for protecting your Backstage installation from external and unauthorized access. The sign-in system in Backstage does not exist to limit access, only to inform the system of the identity of the user. There are some plugins that have more fine-grained access control through the permissions system, but the primary purpose of that system is to restrict access to resources for internal users rather than Backstage as a whole. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWS’s ALB, GCP’s IAP, or Cloudflare Access. +:::info +This section assumes that you are using the +[new backend system](../backend-system/index.md) and at least Backstage release [version 1.24](../releases/v1.24.0.md). Before that Backstage did not come with built-in protection against unauthorized access and you were required to deploy it in a protected environment. +::: -Other responsibilities include protecting the integrity of configuration files as it may otherwise be possible to introduce vulnerable configurations, as well as the confidentiality of configured secrets related to Backstage as these typically include authentication details to third party systems. +Backstage is primarily designed to be deployed in a protected environment rather than being exposed to the public internet. From a confidentiality and integrity perspective, Backstage is designed to protect against unauthorized access to data and to ensure that data is not tampered with. However, Backstage does not provide more than rudimentary protection against denial of service attacks, and it is the responsibility of the operator to ensure that the Backstage deployment is protected against such attacks. A common and recommended way to protect a Backstage deployment from unauthorized access is to deploy it behind an authenticating proxy such as AWS’s ALB, GCP’s IAP, or Cloudflare Access. + +Users that are signed-in in to Backstage generally have full access to all information and actions. If more fine-grained control is required, the [permissions system](../permissions/overview.md) should be enabled and configured to restrict access as necessary. + +An operator is responsible for protecting the integrity of configuration files as it may otherwise be possible to introduce vulnerable configurations, as well as the confidentiality of configured secrets related to Backstage as these typically include authentication details to third party systems. The operator is ultimately responsible for auditing usage of internal and external plugins as these run on the host system and have access to configuration and secrets. When installing plugins from sources like NPM, you should vet these in the same way that you would vet any other package installed from that source. @@ -44,17 +51,19 @@ Note that the `UrlReader` system operates with a service context and is not inte Backstage provides authentication of users through the `auth` plugin, which primarily acts as an authorization server for different OAuth 2.0 provider integrations. These integrations can both serve the purpose of signing users into Backstage, as well as providing delegated access to external resources, and are all subject to the common concerns of implementing secure OAuth 2.0 authorization servers. All auth provider integrations are disabled by default, and need to be enabled through configuration in order to be used. For each Backstage installation it is recommended to only enable the minimal set of providers that are in use by that instance. -It is not within scope of the `auth` backend to protect against unauthorized access, that is something that needs to be handled at a deployment level. See the [Operator Responsibilities](#operator-responsibilities) section for more information. - In order to use an auth provider to sign in users into Backstage, it needs to be configured with an [Identity resolver](https://backstage.io/docs/auth/identity-resolver), which is a custom callback implemented in code. The identity resolver is a sensitive part of configuring Backstage and it is important that it always resolves user identities correctly, based on information provided by the authentication provider. There are a number of built-in identity resolvers that can simplify configuration, and it is important that these all resolve users in a secure way, regardless of how they are used. +Backstage also supports authentication through a proxy where the user identity is read from the incoming request from the proxy, which has been decorated by an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` does not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. It’s therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider. + As part of signing in with an identity resolver, a Backstage Token is issued containing the resolved user identity. The tokens are asymmetrically signed JSON Web Tokens, with the public keys available to any service that wishes to verify a token. The signing keys are rotated continuously and are unique to each installation of Backstage, meaning that Backstage Tokens are not shared across installations. The token contains claims for the user identity and ownership information, which can be used to determine what Backstage resources are owned by that user or group. It is important that this token can not be forged outside of the `auth` plugin, with the exception of other plugins deployed in the same backend service or sharing the same database. For a high-security deployment, the `auth` backend should therefore be deployed in a separate service with its own database. The token is used to prove the identity of the user within the Backstage system, and is used throughout Backstage plugins to control access. It is important that the ownership resolution logic is consistent across the entire Backstage ecosystem, with no possibility of misinterpreting the ownership information. -For cross-backend communication, the Backstage Token is typically forwarded or, in strict backend-to-backend communication without a user party, the backend itself issues a service token based on a pre-shared secret which is then validated on the receiving end. There are no unique service identities tied to these tokens at this point, meaning the tokens can be used across all services in a Backstage installation. This is something that we aim to improve in the future. +One of the claims in a user token is the User Identity Proof or `uip`. This is an additional signature of the token that allows for offline token transformation. By replacing the original signature with the `uip` the token is still proof of a user identity, but it no longer acts as a full access token and will be rejected by most plugin endpoints. Plugins can explicitly allow use of this limited token where required, but this should only be used when necessary when a full token is not available, and ideally just for read-only access. Use-cases for limited users tokens include cookie authentication of static assets, storage of user identity proofs in a database, and similar. -Backstage also supports authentication through a proxy where the user identity is read from the incoming request from the proxy, which has been decorated by an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` does not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. It’s therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider. +The communication across backend plugins uses a similar authentication scheme to the user authentication. Each backend plugin generates and publishes its own set of keys that it uses to sign its tokens, and the public keys are shared with all other plugins for verification. The expected location of each plugin's published JWKS is determined by the `DiscoveryService` implementation in the backend, which means that it is vital for any custom implementation of that service to be careful with user input. The tokens signed by each plugin contain both the source and target plugin ID, which means that the token can not be reused to access other plugins. + +When forwarding a user identity in a call across backend plugins only the limited user token with `uip` is used, wrapped in a new plugin token that is signed by the calling plugin. This means that the receiving plugin can trust the user identity, but it is not able to make further calls on behalf of the user except for with the plugins that it is authorized to call. That is except for any endpoints in other plugins that accept limited user tokens, which is a reason to avoid accepting them when possible. ## Catalog From 812dff05b9b99b7665c9d09bd00bb4a86799ca06 Mon Sep 17 00:00:00 2001 From: Mike Ball Date: Sun, 5 May 2024 20:27:35 -0400 Subject: [PATCH 233/567] chore(plugin-template): add missing semicolon This adds a previously-missing semicolon to the `ExampleFetchComponent.test.tsx` file templated out by `backstage-cli new --select plugin`. Signed-off-by: Mike Ball --- .changeset/tasty-moles-jog.md | 5 +++++ .../ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/tasty-moles-jog.md diff --git a/.changeset/tasty-moles-jog.md b/.changeset/tasty-moles-jog.md new file mode 100644 index 0000000000..f3ac3b63e4 --- /dev/null +++ b/.changeset/tasty-moles-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add previously-missing semicolon to tsx file templated by `backstage-cli new --select plugin`. diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs index 1fe424ab9b..1e746ff39a 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs @@ -8,7 +8,7 @@ describe('ExampleFetchComponent', () => { // Wait for the table to render const table = await screen.findByRole('table'); - const nationality = screen.getAllByText('GB') + const nationality = screen.getAllByText('GB'); // Assert that the table contains the expected user data expect(table).toBeInTheDocument(); expect(screen.getByAltText('Carolyn')).toBeInTheDocument(); From a35ef27eadeda1b47ded934c87709200254e30ce Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Sun, 5 May 2024 21:53:57 -0300 Subject: [PATCH 234/567] fix(plugins/scaffolder-backend-module-github): only overriding default comit author when provide it Signed-off-by: Matheus Castiglioni --- .../api-report.md | 2 + .../githubPullRequest.examples.test.ts | 129 +++++++---- .../src/actions/githubPullRequest.examples.ts | 42 +++- .../src/actions/githubPullRequest.test.ts | 215 ++++++++++++++---- .../src/actions/githubPullRequest.ts | 49 +++- .../src/module.ts | 1 + .../actions/builtin/createBuiltinActions.ts | 1 + 7 files changed, 351 insertions(+), 88 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index 07f5146901..cac2a3fee2 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -128,6 +128,8 @@ export interface CreateGithubPullRequestActionOptions { } | null>; } >; + // (undocumented) + config: Config; githubCredentialsProvider?: GithubCredentialsProvider; integrations: ScmIntegrationRegistry; } diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts index e881ae3192..26469dfcb4 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts @@ -103,6 +103,7 @@ describe('publish:github:pull-request examples', () => { integrations, githubCredentialsProvider, clientFactory, + config, }); }); @@ -134,10 +135,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -178,10 +175,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -221,10 +214,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -264,10 +253,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -314,10 +299,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -358,10 +339,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -402,10 +379,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -452,10 +425,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -502,10 +471,6 @@ describe('publish:github:pull-request examples', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -520,7 +485,7 @@ describe('publish:github:pull-request examples', () => { expect(mockContext.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); - it('Create a pull request with a git author', async () => { + it('Create a pull request with a git author name and email', async () => { const input = yaml.parse(examples[9].example).steps[0].input; await action.handler({ @@ -564,6 +529,94 @@ describe('publish:github:pull-request examples', () => { expect(mockContext.output).toHaveBeenCalledWith('pullRequestNumber', 123); }); + it('Create a pull request with a git author name', async () => { + const input = yaml.parse(examples[10].example).steps[0].input; + + await action.handler({ + ...mockContext, + workspacePath, + input, + }); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Create my new app', + body: 'This PR is really good', + head: 'new-app', + draft: undefined, + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'scaffolder@backstage.io', + name: 'Foo Bar', + }, + }, + ], + }); + + expect(fakeClient.rest.pulls.requestReviewers).not.toHaveBeenCalled(); + expect(mockContext.output).toHaveBeenCalledTimes(3); + expect(mockContext.output).toHaveBeenCalledWith('targetBranchName', 'main'); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://github.com/myorg/myrepo/pull/123', + ); + expect(mockContext.output).toHaveBeenCalledWith('pullRequestNumber', 123); + }); + + it('Create a pull request with a git author email', async () => { + const input = yaml.parse(examples[11].example).steps[0].input; + + await action.handler({ + ...mockContext, + workspacePath, + input, + }); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Create my new app', + body: 'This PR is really good', + head: 'new-app', + draft: undefined, + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'foo@bar.example', + name: 'Scaffolder', + }, + }, + ], + }); + + expect(fakeClient.rest.pulls.requestReviewers).not.toHaveBeenCalled(); + expect(mockContext.output).toHaveBeenCalledTimes(3); + expect(mockContext.output).toHaveBeenCalledWith('targetBranchName', 'main'); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://github.com/myorg/myrepo/pull/123', + ); + expect(mockContext.output).toHaveBeenCalledWith('pullRequestNumber', 123); + }); + it('Create a pull request with all parameters', async () => { mockDir.setContent({ [workspacePath]: { @@ -571,7 +624,7 @@ describe('publish:github:pull-request examples', () => { irrelevant: { 'bar.txt': 'Nothing to see here' }, }, }); - const input = yaml.parse(examples[10].example).steps[0].input; + const input = yaml.parse(examples[12].example).steps[0].input; await action.handler({ ...mockContext, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts index f3042a7e15..b6d31e732b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts @@ -179,7 +179,7 @@ export const examples: TemplateExample[] = [ }), }, { - description: 'Create a pull request with a git author', + description: 'Create a pull request with a git author name and email', example: yaml.stringify({ steps: [ { @@ -197,6 +197,46 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a pull request with a git author name', + example: yaml.stringify({ + steps: [ + { + action: 'publish:github:pull-request', + name: 'Create a pull reuqest', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + branchName: 'new-app', + title: 'Create my new app', + description: 'This PR is really good', + // gitAuthorEmail will be 'scaffolder@backstage.io' + // once one author attribute has been set we need to set both + gitAuthorName: 'Foo Bar', + }, + }, + ], + }), + }, + { + description: 'Create a pull request with a git author email', + example: yaml.stringify({ + steps: [ + { + action: 'publish:github:pull-request', + name: 'Create a pull reuqest', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + branchName: 'new-app', + title: 'Create my new app', + description: 'This PR is really good', + // gitAuthorName will be 'Scaffolder' + // once one author attribute has been set we need to set both + gitAuthorEmail: 'foo@bar.example', + }, + }, + ], + }), + }, { description: 'Create a pull request with all parameters', example: yaml.stringify({ diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index a38b070bdf..6f730fabcf 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -15,7 +15,7 @@ */ import { createRootLogger } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; +import { Config, ConfigReader } from '@backstage/config'; import { GithubCredentialsProvider, ScmIntegrations, @@ -46,6 +46,8 @@ describe('createPublishGithubPullRequestAction', () => { pulls: { requestReviewers: jest.Mock }; }; }; + let config: Config; + let integrations: ScmIntegrations; const mockDir = createMockDirectory(); const workspacePath = mockDir.resolve('workspace'); @@ -53,7 +55,8 @@ describe('createPublishGithubPullRequestAction', () => { beforeEach(() => { mockDir.clear(); - const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); + config = new ConfigReader({}); + integrations = ScmIntegrations.fromConfig(config); fakeClient = { createPullRequest: jest.fn(async (_: any) => { return { @@ -84,6 +87,7 @@ describe('createPublishGithubPullRequestAction', () => { integrations, githubCredentialsProvider, clientFactory, + config, }); }); @@ -155,10 +159,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -216,10 +216,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -279,10 +275,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -334,10 +326,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -465,10 +453,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '120000', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -518,10 +502,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100755', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -581,10 +561,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100755', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -640,10 +616,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], }); @@ -689,10 +661,6 @@ describe('createPublishGithubPullRequestAction', () => { mode: '100644', }, }, - author: { - email: 'scaffolder@backstage.io', - name: 'Scaffolder', - }, }, ], forceFork: true, @@ -700,7 +668,7 @@ describe('createPublishGithubPullRequestAction', () => { }); }); - describe('with author', () => { + describe('with author name and email', () => { let input: GithubPullRequestActionInput; let ctx: ActionContext; @@ -721,7 +689,7 @@ describe('createPublishGithubPullRequestAction', () => { ctx = createMockActionContext({ input, workspacePath }); }); - it('creates a pull request', async () => { + it('creates a pull request with author name', async () => { await instance.handler(ctx); expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ @@ -749,4 +717,171 @@ describe('createPublishGithubPullRequestAction', () => { }); }); }); + + describe('with author name', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + gitAuthorName: 'Foo Bar', + }; + + mockDir.setContent({ + [workspacePath]: { 'file.txt': 'Hello there!' }, + }); + + ctx = createMockActionContext({ input, workspacePath }); + }); + + it('creates a pull request with author name', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'scaffolder@backstage.io', + name: 'Foo Bar', + }, + }, + ], + }); + }); + }); + + describe('with author email', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + gitAuthorEmail: 'foo@bar.example', + }; + + mockDir.setContent({ + [workspacePath]: { 'file.txt': 'Hello there!' }, + }); + + ctx = createMockActionContext({ input, workspacePath }); + }); + + it('creates a pull request with author name', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'foo@bar.example', + name: 'Scaffolder', + }, + }, + ], + }); + }); + }); + + describe('with author from config file', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + }; + + mockDir.setContent({ + [workspacePath]: { 'file.txt': 'Hello there!' }, + }); + + ctx = createMockActionContext({ input, workspacePath }); + }); + + it('creates a pull request with author name', async () => { + config = new ConfigReader({ + scaffolder: { + defaultAuthor: { + name: 'Config', + email: 'config@file.example', + }, + }, + }); + + const clientFactory = jest.fn(async () => fakeClient as any); + const githubCredentialsProvider: GithubCredentialsProvider = { + getCredentials: jest.fn(), + }; + + const instanceWithConfig = createPublishGithubPullRequestAction({ + integrations, + githubCredentialsProvider, + clientFactory, + config, + }); + + await instanceWithConfig.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'config@file.example', + name: 'Config', + }, + }, + ], + }); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 0a256d36d5..3c40361af3 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -32,6 +32,7 @@ import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { getOctokitOptions } from './helpers'; import { examples } from './githubPullRequest.examples'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; export type Encoding = 'utf-8' | 'base64'; @@ -100,6 +101,7 @@ export interface CreateGithubPullRequestActionOptions { } | null>; } >; + config: Config; } type GithubPullRequest = { @@ -119,6 +121,7 @@ export const createPublishGithubPullRequestAction = ( integrations, githubCredentialsProvider, clientFactory = defaultClientFactory, + config, } = options; return createTemplateAction<{ @@ -229,13 +232,13 @@ export const createPublishGithubPullRequestAction = ( type: 'string', title: 'Default Author Name', description: - "Sets the default author name for the commit. The default value is 'Scaffolder'", + "Sets the default author name for the commit. The default value is the authenticated user or 'Scaffolder'", }, gitAuthorEmail: { type: 'string', title: 'Default Author Email', description: - "Sets the default author email for the commit. The default value is 'scaffolder@backstage.io'", + "Sets the default author email for the commit. The default value is the authenticated user or 'scaffolder@backstage.io'", }, }, }, @@ -276,8 +279,8 @@ export const createPublishGithubPullRequestAction = ( commitMessage, update, forceFork, - gitAuthorEmail = 'scaffolder@backstage.io', - gitAuthorName = 'Scaffolder', + gitAuthorEmail, + gitAuthorName, } = ctx.input; const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); @@ -343,11 +346,10 @@ export const createPublishGithubPullRequestAction = ( changes: [ { files, - commit: commitMessage ?? title, - author: { - name: gitAuthorName, - email: gitAuthorEmail, - }, + commit: + commitMessage ?? + config.getOptionalString('scaffolder.defaultCommitMessage') ?? + title, }, ], body: description, @@ -357,6 +359,35 @@ export const createPublishGithubPullRequestAction = ( forceFork, }; + const gitAuthorInfo = { + name: + gitAuthorName ?? + config.getOptionalString('scaffolder.defaultAuthor.name'), + email: + gitAuthorEmail ?? + config.getOptionalString('scaffolder.defaultAuthor.email'), + }; + + if (gitAuthorInfo.name || gitAuthorInfo.email) { + if (Array.isArray(createOptions.changes)) { + createOptions.changes = createOptions.changes.map(change => ({ + ...change, + author: { + name: gitAuthorInfo.name || 'Scaffolder', + email: gitAuthorInfo.email || 'scaffolder@backstage.io', + }, + })); + } else { + createOptions.changes = { + ...createOptions.changes, + author: { + name: gitAuthorInfo.name || 'Scaffolder', + email: gitAuthorInfo.email || 'scaffolder@backstage.io', + }, + }; + } + } + if (targetBranchName) { createOptions.base = targetBranchName; } diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 79c954a6dd..1817a68bfb 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -89,6 +89,7 @@ export const githubModule = createBackendModule({ createPublishGithubPullRequestAction({ integrations, githubCredentialsProvider, + config, }), ); }, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index a017c2e224..673776bec9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -178,6 +178,7 @@ export const createBuiltinActions = ( createPublishGithubPullRequestAction({ integrations, githubCredentialsProvider, + config, }), createPublishGitlabAction({ integrations, From b000ee6fa2f28cbcf0edcf9ae15703b352fc59ac Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Sun, 5 May 2024 22:12:18 -0300 Subject: [PATCH 235/567] fix(plugins/scaffolder-backend-module-github): api report config undocumented Signed-off-by: Matheus Castiglioni --- plugins/scaffolder-backend-module-github/api-report.md | 1 - .../src/actions/githubPullRequest.ts | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index cac2a3fee2..4f3cd819ad 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -128,7 +128,6 @@ export interface CreateGithubPullRequestActionOptions { } | null>; } >; - // (undocumented) config: Config; githubCredentialsProvider?: GithubCredentialsProvider; integrations: ScmIntegrationRegistry; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 3c40361af3..cc5e1280ef 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -101,6 +101,9 @@ export interface CreateGithubPullRequestActionOptions { } | null>; } >; + /** + * An instance of {@link @backstage/config#Config} that will be used in the action. + */ config: Config; } From e5486988c06a6757eb918ce972f4767c22cfa487 Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Sun, 5 May 2024 22:39:44 -0300 Subject: [PATCH 236/567] test(plugins/scaffolder-backend-module-github): adding missing author data priority test Signed-off-by: Matheus Castiglioni --- .../src/actions/githubPullRequest.test.ts | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index 6f730fabcf..dd7641ce4c 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -689,7 +689,7 @@ describe('createPublishGithubPullRequestAction', () => { ctx = createMockActionContext({ input, workspacePath }); }); - it('creates a pull request with author name', async () => { + it('creates a pull request', async () => { await instance.handler(ctx); expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ @@ -738,7 +738,7 @@ describe('createPublishGithubPullRequestAction', () => { ctx = createMockActionContext({ input, workspacePath }); }); - it('creates a pull request with author name', async () => { + it('creates a pull request', async () => { await instance.handler(ctx); expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ @@ -787,7 +787,7 @@ describe('createPublishGithubPullRequestAction', () => { ctx = createMockActionContext({ input, workspacePath }); }); - it('creates a pull request with author name', async () => { + it('creates a pull request', async () => { await instance.handler(ctx); expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ @@ -835,7 +835,7 @@ describe('createPublishGithubPullRequestAction', () => { ctx = createMockActionContext({ input, workspacePath }); }); - it('creates a pull request with author name', async () => { + it('creates a pull request with default config attributes', async () => { config = new ConfigReader({ scaffolder: { defaultAuthor: { @@ -884,4 +884,75 @@ describe('createPublishGithubPullRequestAction', () => { }); }); }); + + describe('with author attributes and config file', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + gitAuthorEmail: 'foo@bar.example', + gitAuthorName: 'Foo Bar', + }; + + mockDir.setContent({ + [workspacePath]: { 'file.txt': 'Hello there!' }, + }); + + ctx = createMockActionContext({ input, workspacePath }); + }); + + it('creates a pull request with using author name and email from input', async () => { + config = new ConfigReader({ + scaffolder: { + defaultAuthor: { + name: 'Config', + email: 'config@file.example', + }, + }, + }); + + const clientFactory = jest.fn(async () => fakeClient as any); + const githubCredentialsProvider: GithubCredentialsProvider = { + getCredentials: jest.fn(), + }; + + const instanceWithConfig = createPublishGithubPullRequestAction({ + integrations, + githubCredentialsProvider, + clientFactory, + config, + }); + + await instanceWithConfig.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'foo@bar.example', + name: 'Foo Bar', + }, + }, + ], + }); + }); + }); }); From e96202968cfb06c768df69bee98dfd3f2cd7afbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 6 May 2024 07:13:26 +0200 Subject: [PATCH 237/567] Update .changeset/tasty-moles-jog.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/tasty-moles-jog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tasty-moles-jog.md b/.changeset/tasty-moles-jog.md index f3ac3b63e4..f28b77337e 100644 --- a/.changeset/tasty-moles-jog.md +++ b/.changeset/tasty-moles-jog.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Add previously-missing semicolon to tsx file templated by `backstage-cli new --select plugin`. +Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`. From fba2993675b5609ee75efc880bf49b1878b03819 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 6 May 2024 11:44:30 +0200 Subject: [PATCH 238/567] Update docs/features/software-templates/writing-templates.md Signed-off-by: Patrik Oldsberg --- docs/features/software-templates/writing-templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index d67f886e12..bf21b91746 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -759,7 +759,7 @@ Allow to load a directory on your local file system that contains a template and ![template editor load dir](../../assets/software-templates/template-editor-load-dir.png) -if you complete the form in the right side and click on `Create` button, the template will be executed in dry-run mode and the result will be shown in the `Dry-run result` drawer that will pop-up at the bottom of the screen. +If you complete the form in the right side and click on `Create` button, the template will be executed in dry-run mode and the result will be shown in the `Dry-run result` drawer that will pop-up at the bottom of the screen. Here we could find all the file system results of the template execution as well as the logs of each action that was executed. From 2f09d4b7cc04893649b69eb48a432e528cbe3f58 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 6 May 2024 11:47:18 +0200 Subject: [PATCH 239/567] Apply suggestions from code review Signed-off-by: Patrik Oldsberg --- packages/core-components/src/layout/Content/Content.stories.tsx | 2 +- .../src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx | 2 +- .../src/layout/HeaderLabel/HeaderLabel.stories.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-components/src/layout/Content/Content.stories.tsx b/packages/core-components/src/layout/Content/Content.stories.tsx index f8bf055145..fcf8858adc 100644 --- a/packages/core-components/src/layout/Content/Content.stories.tsx +++ b/packages/core-components/src/layout/Content/Content.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. diff --git a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx index 0d185aeb11..dc50456f7e 100644 --- a/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx +++ b/packages/core-components/src/layout/HeaderActionMenu/HeaderActionMenu.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. diff --git a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx index 80baa83a4f..58566a27d2 100644 --- a/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx +++ b/packages/core-components/src/layout/HeaderLabel/HeaderLabel.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. From e754da5127336da75e6caaf2133ec0aa71ec44fa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 6 May 2024 11:54:57 +0200 Subject: [PATCH 240/567] e2e-test: remove unused puppeteer dependency Signed-off-by: Patrik Oldsberg --- packages/e2e-test/package.json | 1 - yarn.lock | 259 ++++++--------------------------- 2 files changed, 42 insertions(+), 218 deletions(-) diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index a41c724d5a..e4ddf47d73 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -43,7 +43,6 @@ "@backstage/cli": "workspace:^", "@types/fs-extra": "^11.0.0", "@types/node": "^18.17.8", - "@types/puppeteer": "^7.0.0", "nodemon": "^3.0.1" }, "nodemonConfig": { diff --git a/yarn.lock b/yarn.lock index ea85c43c11..0eb13f4629 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12254,24 +12254,6 @@ __metadata: languageName: node linkType: hard -"@puppeteer/browsers@npm:2.2.1": - version: 2.2.1 - resolution: "@puppeteer/browsers@npm:2.2.1" - dependencies: - debug: 4.3.4 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.4.0 - semver: 7.6.0 - tar-fs: 3.0.5 - unbzip2-stream: 1.4.3 - yargs: 17.7.2 - bin: - browsers: lib/cjs/main-cli.js - checksum: c2ec8bac9978ae6279d67442a3a81c517db1e172bd4603d1983eb48de12b088d8b565b1817abe21ba6df76be9a68e3fc543d4c7111c964a93f3ac9b14f7c76e5 - languageName: node - linkType: hard - "@radix-ui/primitive@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/primitive@npm:1.0.1" @@ -16495,15 +16477,6 @@ __metadata: languageName: node linkType: hard -"@types/puppeteer@npm:^7.0.0": - version: 7.0.4 - resolution: "@types/puppeteer@npm:7.0.4" - dependencies: - puppeteer: "*" - checksum: c84a44b054454c13935a9cf0f8983166e238532397af4f321c918d89b43a91f854460e3d0dda122f72c258b444dbcdc04ada950e35adc1938ff3b7831c6fd7a4 - languageName: node - linkType: hard - "@types/qs@npm:*": version: 6.9.6 resolution: "@types/qs@npm:6.9.6" @@ -17081,7 +17054,7 @@ __metadata: languageName: node linkType: hard -"@types/yauzl@npm:^2.10.0, @types/yauzl@npm:^2.9.1": +"@types/yauzl@npm:^2.10.0": version: 2.10.3 resolution: "@types/yauzl@npm:2.10.3" dependencies: @@ -19655,7 +19628,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.2.1, buffer@npm:^5.5.0, buffer@npm:^5.7.1": +"buffer@npm:^5.5.0, buffer@npm:^5.7.1": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -20145,19 +20118,6 @@ __metadata: languageName: node linkType: hard -"chromium-bidi@npm:0.5.17": - version: 0.5.17 - resolution: "chromium-bidi@npm:0.5.17" - dependencies: - mitt: 3.0.1 - urlpattern-polyfill: 10.0.0 - zod: 3.22.4 - peerDependencies: - devtools-protocol: "*" - checksum: 522da996ed5abfb47707583cc24785f9aa05d87bd968dbd520f245cf8972fa3ec102f8d1d72fa07558daa70495d8c6f2bf364d8599eb60b77504e528601d8a30 - languageName: node - linkType: hard - "ci-info@npm:^2.0.0": version: 2.0.0 resolution: "ci-info@npm:2.0.0" @@ -21166,23 +21126,6 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:9.0.0": - version: 9.0.0 - resolution: "cosmiconfig@npm:9.0.0" - dependencies: - env-paths: ^2.2.1 - import-fresh: ^3.3.0 - js-yaml: ^4.1.0 - parse-json: ^5.2.0 - peerDependencies: - typescript: ">=4.9.5" - peerDependenciesMeta: - typescript: - optional: true - checksum: a30c424b53d442ea0bdd24cb1b3d0d8687c8dda4a17ab6afcdc439f8964438801619cdb66e8e79f63b9caa3e6586b60d8bab9ce203e72df6c5e80179b971fe8f - languageName: node - linkType: hard - "cosmiconfig@npm:^6.0.0": version: 6.0.0 resolution: "cosmiconfig@npm:6.0.0" @@ -22423,13 +22366,6 @@ __metadata: languageName: node linkType: hard -"devtools-protocol@npm:0.0.1262051": - version: 0.0.1262051 - resolution: "devtools-protocol@npm:0.0.1262051" - checksum: beaad00059964a661ab056d5e993492742c612c0370c6f08acd91490181c4d4ecf57d316eedb5a37fb6bb59321901d09ce50762f79ea09a50751d86f601b8f8e - languageName: node - linkType: hard - "dezalgo@npm:^1.0.0, dezalgo@npm:^1.0.4": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" @@ -22839,7 +22775,6 @@ __metadata: "@backstage/errors": "workspace:^" "@types/fs-extra": ^11.0.0 "@types/node": ^18.17.8 - "@types/puppeteer": ^7.0.0 chalk: ^4.0.0 commander: ^12.0.0 cross-fetch: ^4.0.0 @@ -24589,23 +24524,6 @@ __metadata: languageName: node linkType: hard -"extract-zip@npm:2.0.1": - version: 2.0.1 - resolution: "extract-zip@npm:2.0.1" - dependencies: - "@types/yauzl": ^2.9.1 - debug: ^4.1.1 - get-stream: ^5.1.0 - yauzl: ^2.10.0 - dependenciesMeta: - "@types/yauzl": - optional: true - bin: - extract-zip: cli.js - checksum: 8cbda9debdd6d6980819cc69734d874ddd71051c9fe5bde1ef307ebcedfe949ba57b004894b585f758b7c9eeeea0e3d87f2dda89b7d25320459c2c9643ebb635 - languageName: node - linkType: hard - "extsprintf@npm:1.3.0": version: 1.3.0 resolution: "extsprintf@npm:1.3.0" @@ -24838,15 +24756,6 @@ __metadata: languageName: node linkType: hard -"fd-slicer@npm:~1.1.0": - version: 1.1.0 - resolution: "fd-slicer@npm:1.1.0" - dependencies: - pend: ~1.2.0 - checksum: c8585fd5713f4476eb8261150900d2cb7f6ff2d87f8feb306ccc8a1122efd152f1783bdb2b8dc891395744583436bfd8081d8e63ece0ec8687eeefea394d4ff2 - languageName: node - linkType: hard - "fecha@npm:^4.2.0": version: 4.2.0 resolution: "fecha@npm:4.2.0" @@ -26713,7 +26622,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": +"http-proxy-agent@npm:^7.0.0": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" dependencies: @@ -26811,7 +26720,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:7.0.4, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2, https-proxy-agent@npm:^7.0.3": +"https-proxy-agent@npm:7.0.4, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2": version: 7.0.4 resolution: "https-proxy-agent@npm:7.0.4" dependencies: @@ -31790,13 +31699,6 @@ __metadata: languageName: node linkType: hard -"mitt@npm:3.0.1": - version: 3.0.1 - resolution: "mitt@npm:3.0.1" - checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1 - languageName: node - linkType: hard - "mixme@npm:^0.5.1": version: 0.5.4 resolution: "mixme@npm:0.5.4" @@ -33532,7 +33434,7 @@ __metadata: languageName: node linkType: hard -"pac-proxy-agent@npm:^7.0.0, pac-proxy-agent@npm:^7.0.1": +"pac-proxy-agent@npm:^7.0.0": version: 7.0.1 resolution: "pac-proxy-agent@npm:7.0.1" dependencies: @@ -35175,13 +35077,6 @@ __metadata: languageName: node linkType: hard -"progress@npm:2.0.3": - version: 2.0.3 - resolution: "progress@npm:2.0.3" - checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7 - languageName: node - linkType: hard - "prom-client@npm:^15.0.0": version: 15.1.2 resolution: "prom-client@npm:15.1.2" @@ -35369,22 +35264,6 @@ __metadata: languageName: node linkType: hard -"proxy-agent@npm:6.4.0": - version: 6.4.0 - resolution: "proxy-agent@npm:6.4.0" - dependencies: - agent-base: ^7.0.2 - debug: ^4.3.4 - http-proxy-agent: ^7.0.1 - https-proxy-agent: ^7.0.3 - lru-cache: ^7.14.1 - pac-proxy-agent: ^7.0.1 - proxy-from-env: ^1.1.0 - socks-proxy-agent: ^8.0.2 - checksum: 4d3794ad5e07486298902f0a7f250d0f869fa0e92d790767ca3f793a81374ce0ab6c605f8ab8e791c4d754da96656b48d1c24cb7094bfd310a15867e4a0841d7 - languageName: node - linkType: hard - "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" @@ -35467,33 +35346,6 @@ __metadata: languageName: node linkType: hard -"puppeteer-core@npm:22.6.4": - version: 22.6.4 - resolution: "puppeteer-core@npm:22.6.4" - dependencies: - "@puppeteer/browsers": 2.2.1 - chromium-bidi: 0.5.17 - debug: 4.3.4 - devtools-protocol: 0.0.1262051 - ws: 8.16.0 - checksum: 76d4328e7d2a788a7a2dc3a8b19571f8eda842ff990da2fe773c3e5c01236a03856b67d9a2a6ae976f8fa74ab38813e2c7e7c4b15b9715965b2156e9ca1f9a78 - languageName: node - linkType: hard - -"puppeteer@npm:*": - version: 22.6.4 - resolution: "puppeteer@npm:22.6.4" - dependencies: - "@puppeteer/browsers": 2.2.1 - cosmiconfig: 9.0.0 - devtools-protocol: 0.0.1262051 - puppeteer-core: 22.6.4 - bin: - puppeteer: lib/esm/puppeteer/node/cli.js - checksum: 7dcbda7fc3b999f3ee7808d1c67da41be0aeabd66b0831711a46a897a2681d117f044263e57aeb7d0a096c420bb9f5871c5c2b21c38e40d323fa675e1de322c9 - languageName: node - linkType: hard - "pure-color@npm:^1.2.0": version: 1.3.0 resolution: "pure-color@npm:1.3.0" @@ -37844,7 +37696,16 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.6.0, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": +"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 + languageName: node + linkType: hard + +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.6.0 resolution: "semver@npm:7.6.0" dependencies: @@ -37855,15 +37716,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" - bin: - semver: bin/semver.js - checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 - languageName: node - linkType: hard - "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -39604,7 +39456,19 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:3.0.5, tar-fs@npm:^3.0.5": +"tar-fs@npm:^2.0.0": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d + languageName: node + linkType: hard + +"tar-fs@npm:^3.0.5": version: 3.0.5 resolution: "tar-fs@npm:3.0.5" dependencies: @@ -39621,18 +39485,6 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^2.0.0": - version: 2.1.1 - resolution: "tar-fs@npm:2.1.1" - dependencies: - chownr: ^1.1.1 - mkdirp-classic: ^0.5.2 - pump: ^3.0.0 - tar-stream: ^2.1.4 - checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d - languageName: node - linkType: hard - "tar-fs@npm:~2.0.1": version: 2.0.1 resolution: "tar-fs@npm:2.0.1" @@ -39900,7 +39752,7 @@ __metadata: languageName: node linkType: hard -"through@npm:2, through@npm:^2.3.6, through@npm:^2.3.8": +"through@npm:2, through@npm:^2.3.6": version: 2.3.8 resolution: "through@npm:2.3.8" checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd @@ -40757,16 +40609,6 @@ __metadata: languageName: node linkType: hard -"unbzip2-stream@npm:1.4.3": - version: 1.4.3 - resolution: "unbzip2-stream@npm:1.4.3" - dependencies: - buffer: ^5.2.1 - through: ^2.3.8 - checksum: 0e67c4a91f4fa0fc7b4045f8b914d3498c2fc2e8c39c359977708ec85ac6d6029840e97f508675fdbdf21fcb8d276ca502043406f3682b70f075e69aae626d1d - languageName: node - linkType: hard - "undefsafe@npm:^2.0.5": version: 2.0.5 resolution: "undefsafe@npm:2.0.5" @@ -41164,13 +41006,6 @@ __metadata: languageName: node linkType: hard -"urlpattern-polyfill@npm:10.0.0": - version: 10.0.0 - resolution: "urlpattern-polyfill@npm:10.0.0" - checksum: 61d890f151ea4ecf34a3dcab32c65ad1f3cda857c9d154af198260c6e5b2ad96d024593409baaa6d4428dd1ab206c14799bf37fe011117ac93a6a44913ac5aa4 - languageName: node - linkType: hard - "urlpattern-polyfill@npm:^9.0.0": version: 9.0.0 resolution: "urlpattern-polyfill@npm:9.0.0" @@ -42289,7 +42124,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:8.16.0, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.8.0": +"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.8.0": version: 8.16.0 resolution: "ws@npm:8.16.0" peerDependencies: @@ -42580,21 +42415,6 @@ __metadata: languageName: node linkType: hard -"yargs@npm:17.7.2, yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: ^8.0.1 - escalade: ^3.1.1 - get-caller-file: ^2.0.5 - require-directory: ^2.1.1 - string-width: ^4.2.3 - y18n: ^5.0.5 - yargs-parser: ^21.1.1 - checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a - languageName: node - linkType: hard - "yargs@npm:^15.1.0": version: 15.4.1 resolution: "yargs@npm:15.4.1" @@ -42629,13 +42449,18 @@ __metadata: languageName: node linkType: hard -"yauzl@npm:^2.10.0": - version: 2.10.0 - resolution: "yauzl@npm:2.10.0" +"yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - buffer-crc32: ~0.2.3 - fd-slicer: ~1.1.0 - checksum: 7f21fe0bbad6e2cb130044a5d1d0d5a0e5bf3d8d4f8c4e6ee12163ce798fee3de7388d22a7a0907f563ac5f9d40f8699a223d3d5c1718da90b0156da6904022b + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard @@ -42810,7 +42635,7 @@ __metadata: languageName: node linkType: hard -"zod@npm:3.22.4, zod@npm:^3.22.4": +"zod@npm:^3.22.4": version: 3.22.4 resolution: "zod@npm:3.22.4" checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f From 190a4036f98aba2d131963904d310e81c4e42e4a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 10:36:37 +0000 Subject: [PATCH 241/567] fix(deps): update dependency zod to v3.23.6 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0eb13f4629..7f949ceb5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42636,9 +42636,9 @@ __metadata: linkType: hard "zod@npm:^3.22.4": - version: 3.22.4 - resolution: "zod@npm:3.22.4" - checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f + version: 3.23.6 + resolution: "zod@npm:3.23.6" + checksum: f534119e2a54e86bf77e5c6ff630ef4ec50b87dd9d9faf66dc7a663a489d37130b716ebd836cdd9d7fc6e124a1accdc0d53f388243a236c10e632dcc945eaa27 languageName: node linkType: hard From 955c7a94b20102bd903e6e1eca44c65e2bd085f7 Mon Sep 17 00:00:00 2001 From: sishwarya <63347232+sishwarya@users.noreply.github.com> Date: Mon, 6 May 2024 16:35:46 +0530 Subject: [PATCH 242/567] Update deploy.yaml Signed-off-by: sishwarya <63347232+sishwarya@users.noreply.github.com> --- microsite/data/plugins/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/deploy.yaml b/microsite/data/plugins/deploy.yaml index a619a7ba0b..cd96e4b60e 100644 --- a/microsite/data/plugins/deploy.yaml +++ b/microsite/data/plugins/deploy.yaml @@ -10,4 +10,4 @@ npmPackageName: '@digital.ai/plugin-dai-deploy' tags: - ci - cd -addedDate: '2024-04-8' +addedDate: '2024-05-06' From 88480e4ef22f5f0158fd0b82dbc516b9c166f53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 6 May 2024 13:26:24 +0200 Subject: [PATCH 243/567] proxy-backend: require auth by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/empty-spoons-tell.md | 43 +++ .../config/vocabularies/Backstage/accept.txt | 1 + docs/plugins/proxying.md | 23 +- plugins/proxy-backend/config.d.ts | 44 +++ plugins/proxy-backend/package.json | 5 + plugins/proxy-backend/src/alpha.ts | 17 +- .../src/service/router.config.test.ts | 2 +- .../src/service/router.credentials.test.ts | 305 ++++++++++++++++++ plugins/proxy-backend/src/service/router.ts | 94 ++++-- yarn.lock | 5 + 10 files changed, 503 insertions(+), 36 deletions(-) create mode 100644 .changeset/empty-spoons-tell.md create mode 100644 plugins/proxy-backend/src/service/router.credentials.test.ts diff --git a/.changeset/empty-spoons-tell.md b/.changeset/empty-spoons-tell.md new file mode 100644 index 0000000000..8c68cdc721 --- /dev/null +++ b/.changeset/empty-spoons-tell.md @@ -0,0 +1,43 @@ +--- +'@backstage/plugin-proxy-backend': minor +--- + +**BREAKING**: The proxy backend plugin is now protected by Backstage auth, by +default. Unless specifically configured (see below), all proxy endpoints will +reject requests immediately unless a valid Backstage user or service token is +passed along with the request. This aligns the proxy with how other Backstage +backends behave out of the box, and serves to protect your upstreams from +unauthorized access. + +A proxy configuration section can now look as follows: + +```yaml +proxy: + endpoints: + '/pagerduty': + target: https://api.pagerduty.com + credentials: require # NEW! + headers: + Authorization: Token token=${PAGERDUTY_TOKEN} +``` + +There are three possible `credentials` settings at this point: + +- `require`: Callers must provide Backstage user or service credentials with + each request. The credentials are not forwarded to the proxy target. +- `forward`: Callers must provide Backstage user or service credentials with + each request, and those credentials are forwarded to the proxy target. +- `dangerously-allow-unauthenticated`: No Backstage credentials are required to + access this proxy target. The target can still apply its own credentials + checks, but the proxy will not help block non-Backstage-blessed callers. If + you also add `allowedHeaders: ['Authorization']` to an endpoint configuration, + then the Backstage token (if provided) WILL be forwarded. + +The value `dangerously-allow-unauthenticated` was the old default. + +The value `require` is the new default, so requests that were previously +permitted may now start resulting in `401 Unauthorized` responses. + +If you have proxy endpoints that require unauthenticated access still, please +add `credentials: dangerously-allow-unauthenticated` to their declarations in +your app-config. diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index aa11568564..77dd930a73 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -447,6 +447,7 @@ unregistering unregistration untracked upsert +upstreams upvote URIs url diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 40d155e13b..0f37eec982 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -18,12 +18,7 @@ The plugin is already added to a default Backstage project. In `packages/backend/src/index.ts`: ```ts -const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); - -const service = createServiceBuilder(module) - .loadConfig(configReader) - /** ... other routers ... */ - .addRouter('/proxy', await proxy(proxyEnv)); +backend.add(import('@backstage/plugin-proxy-backend/alpha')); ``` ## Configuration @@ -40,6 +35,7 @@ proxy: /simple-example: http://simple.example.com:8080 '/larger-example/v1': target: http://larger.example.com:8080/svc.v1 + credentials: require headers: Authorization: ${EXAMPLE_AUTH_HEADER} # ...or interpolating a value into part of a string, @@ -56,6 +52,19 @@ backend requests to `/api/proxy/simple-example/...` and The value inside each route is either a simple URL string, or an object on the format accepted by [http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). +Additionally, it has an optional `credentials` key which can have the following +values: + +- `require`: Callers must provide Backstage user or service credentials with + each request. The credentials are not forwarded to the proxy target. This is + the default. +- `forward`: Callers must provide Backstage user or service credentials with + each request, and those credentials are forwarded to the proxy target. +- `dangerously-allow-unauthenticated`: No Backstage credentials are required to + access this proxy target. The target can still apply its own credentials + checks, but the proxy will not help block non-Backstage-blessed callers. If + you also add `allowedHeaders: ['Authorization']` to an endpoint configuration, + then the Backstage token (if provided) WILL be forwarded. If the value is a string, it is assumed to correspond to: @@ -64,6 +73,7 @@ target: changeOrigin: true pathRewrite: '^/': '/' +credentials: require ``` When the target is an object, it is given verbatim to `http-proxy-middleware` @@ -76,6 +86,7 @@ except with the following caveats for convenience: `'^/api/proxy/larger-example/v1/': '/'` is added. That means that a request to `/api/proxy/larger-example/v1/some/path` will be translated to a request to `http://larger.example.com:8080/svc.v1/some/path`. +- If `credentials` is not specified, it is set to `require`. There are also additional settings: diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index dd65aa8e75..710ff6d23f 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -71,6 +71,28 @@ export interface Config { * and headers that are set by the proxy will be forwarded. */ allowedHeaders?: string[]; + /** + * The credentials policy to apply. + * + * @remarks + * + * The values are as follows: + * + * - 'require': Callers must provide Backstage user or service + * credentials with each request. The credentials are not + * forwarded to the proxy target. + * - 'forward': Callers must provide Backstage user or service + * credentials with each request, and those credentials are + * forwarded to the proxy target. + * - 'dangerously-allow-unauthenticated': No Backstage credentials + * are required to access this proxy target. The target can still + * apply its own credentials checks, but the proxy will not help + * block non-Backstage-blessed callers. + */ + credentials?: + | 'require' + | 'forward' + | 'dangerously-allow-unauthenticated'; }; }; } & { @@ -121,6 +143,28 @@ export interface Config { * and headers that are set by the proxy will be forwarded. */ allowedHeaders?: string[]; + /** + * The credentials policy to apply. + * + * @remarks + * + * The values are as follows: + * + * - 'require': Callers must provide Backstage user or service + * credentials with each request. The credentials are not + * forwarded to the proxy target. + * - 'forward': Callers must provide Backstage user or service + * credentials with each request, and those credentials are + * forwarded to the proxy target. + * - 'dangerously-allow-unauthenticated': No Backstage credentials + * are required to access this proxy target. The target can still + * apply its own credentials checks, but the proxy will not help + * block non-Backstage-blessed callers. + */ + credentials?: + | 'require' + | 'forward' + | 'dangerously-allow-unauthenticated'; }; }; } diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 9430f7120d..aa93b47c5e 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -52,6 +52,7 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -64,14 +65,18 @@ "yup": "^1.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", + "@backstage/errors": "workspace:^", "@types/http-proxy-middleware": "^1.0.0", "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", "msw": "^1.0.0", + "node-fetch": "^2.6.7", + "portfinder": "^1.0.32", "supertest": "^6.1.3" }, "configSchema": "config.d.ts" diff --git a/plugins/proxy-backend/src/alpha.ts b/plugins/proxy-backend/src/alpha.ts index c1d55b84b3..339e42cd94 100644 --- a/plugins/proxy-backend/src/alpha.ts +++ b/plugins/proxy-backend/src/alpha.ts @@ -19,7 +19,7 @@ import { createBackendPlugin, coreServices, } from '@backstage/backend-plugin-api'; -import { createRouter } from './service/router'; +import { createRouterInternal } from './service/router'; /** * The proxy backend plugin. @@ -37,16 +37,11 @@ export default createBackendPlugin({ httpRouter: coreServices.httpRouter, }, async init({ config, discovery, logger, httpRouter }) { - httpRouter.use( - await createRouter({ - config, - discovery, - logger: loggerToWinstonLogger(logger), - }), - ); - httpRouter.addAuthPolicy({ - allow: 'unauthenticated', - path: '/', + await createRouterInternal({ + config, + discovery, + logger: loggerToWinstonLogger(logger), + httpRouterService: httpRouter, }); }, }); diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index 8643ee67c0..4f8420fc68 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -27,7 +27,7 @@ import request from 'supertest'; import { createRouter } from './router'; // this test is stored in its own file to work around the mocked -// http-proxy-middleware module used in the rest of the tests +// http-proxy-middleware module used in the main test file describe('createRouter reloadable configuration', () => { const server = setupServer( diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts new file mode 100644 index 0000000000..1496b6ffb6 --- /dev/null +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -0,0 +1,305 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; +import { ResponseError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import fetch from 'node-fetch'; +import portFinder from 'portfinder'; + +// this test is stored in its own file to work around the mocked +// http-proxy-middleware module used in the main test file + +describe('credentials', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + it('handles all valid credentials settings', async () => { + const host = 'localhost'; + const port = await portFinder.getPortPromise(); + const baseUrl = `http://${host}:${port}`; + + const config = { + backend: { + baseUrl, + listen: { host, port }, + auth: { + externalAccess: [ + { + type: 'static', + options: { + token: 'static-token', + subject: 'static-subject', + }, + }, + ], + }, + }, + proxy: { + endpoints: { + '/simple': 'http://target.com', + '/default': { + target: 'http://target.com', + }, + '/require': { + target: 'http://target.com', + credentials: 'require', + }, + '/forward': { + target: 'http://target.com', + credentials: 'forward', + }, + '/dangerously-allow-unauthenticated--no-forward': { + target: 'http://target.com', + credentials: 'dangerously-allow-unauthenticated', + }, + '/dangerously-allow-unauthenticated--with-forward': { + target: 'http://target.com', + credentials: 'dangerously-allow-unauthenticated', + allowedHeaders: ['Authorization'], + }, + }, + }, + }; + + worker.use( + rest.all(`${baseUrl}/*`, req => req.passthrough()), + rest.get('http://target.com/*', (req, res, ctx) => { + const auth = req.headers.get('authorization'); + return res( + ctx.status(200), + ctx.json({ payload: { forwardedAuthorization: auth ?? false } }), + ); + }), + ); + + async function call(options: { + endpoint: string; + authorization: string | false; + }): Promise { + const { endpoint, authorization } = options; + return fetch(`${baseUrl}/api/proxy/${endpoint}/just-some-path`, { + headers: authorization ? { Authorization: authorization } : {}, + }).then(async res => { + if (!res.ok) { + throw await ResponseError.fromResponse(res); + } + return res.json(); + }); + } + + // Create an actual backend instead of a test backend, because we want to + // use the real HTTP server that provides the protection middleware etc. A + // bit harder to test, but at least we can use static external access tokens + // for it. + const backend = createBackend(); + backend.add(import('../alpha')); + backend.add(mockServices.rootConfig.factory({ data: config })); + backend.add(mockServices.rootLogger.factory()); + await backend.start(); + + try { + // simple credentials config + await expect( + call({ endpoint: 'simple', authorization: false }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Missing credentials', + name: 'AuthenticationError', + }, + }, + }); + await expect( + call({ endpoint: 'simple', authorization: 'Bearer static-token' }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: false, + }, + }); + await expect( + call({ endpoint: 'simple', authorization: 'Bearer not-valid' }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Illegal token', + name: 'AuthenticationError', + }, + }, + }); + + // default credentials config + await expect( + call({ endpoint: 'default', authorization: false }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Missing credentials', + name: 'AuthenticationError', + }, + }, + }); + await expect( + call({ endpoint: 'default', authorization: 'Bearer static-token' }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: false, + }, + }); + await expect( + call({ endpoint: 'default', authorization: 'Bearer not-valid' }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Illegal token', + name: 'AuthenticationError', + }, + }, + }); + + // require credentials config + await expect( + call({ endpoint: 'require', authorization: false }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Missing credentials', + name: 'AuthenticationError', + }, + }, + }); + await expect( + call({ endpoint: 'require', authorization: 'Bearer static-token' }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: false, + }, + }); + await expect( + call({ endpoint: 'require', authorization: 'Bearer not-valid' }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Illegal token', + name: 'AuthenticationError', + }, + }, + }); + + // forward credentials config + await expect( + call({ endpoint: 'forward', authorization: false }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Missing credentials', + name: 'AuthenticationError', + }, + }, + }); + await expect( + call({ endpoint: 'forward', authorization: 'Bearer static-token' }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: 'Bearer static-token', + }, + }); + await expect( + call({ endpoint: 'forward', authorization: 'Bearer not-valid' }), + ).rejects.toMatchObject({ + body: { + error: { + message: 'Illegal token', + name: 'AuthenticationError', + }, + }, + }); + + // dangerously-allow-unauthenticated credentials config, no forwarding + await expect( + call({ + endpoint: 'dangerously-allow-unauthenticated--no-forward', + authorization: false, + }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: false, + }, + }); + await expect( + call({ + endpoint: 'dangerously-allow-unauthenticated--no-forward', + authorization: 'Bearer static-token', + }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: false, + }, + }); + await expect( + call({ + endpoint: 'dangerously-allow-unauthenticated--no-forward', + authorization: 'Bearer not-valid', + }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: false, + }, + }); + + // dangerously-allow-unauthenticated credentials config, with forwarding + await expect( + call({ + endpoint: 'dangerously-allow-unauthenticated--with-forward', + authorization: false, + }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: false, + }, + }); + await expect( + call({ + endpoint: 'dangerously-allow-unauthenticated--with-forward', + authorization: 'Bearer static-token', + }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: 'Bearer static-token', + }, + }); + await expect( + call({ + endpoint: 'dangerously-allow-unauthenticated--with-forward', + authorization: 'Bearer not-valid-for-backstage-but-valid-for-target', + }), + ).resolves.toMatchObject({ + payload: { + forwardedAuthorization: + 'Bearer not-valid-for-backstage-but-valid-for-target', + }, + }); + } finally { + await backend.stop(); + } + }); +}); diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index 96b9beceee..6a513678f0 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -26,6 +26,8 @@ import { import { Logger } from 'winston'; import http from 'http'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { JsonObject } from '@backstage/types'; +import { HttpRouterService } from '@backstage/backend-plugin-api'; // A list of headers that are always forwarded to the proxy targets. const safeForwardHeaders = [ @@ -71,9 +73,37 @@ export function buildMiddleware( route: string, config: string | ProxyConfig, reviveConsumedRequestBodies?: boolean, + httpRouterService?: HttpRouterService, ): RequestHandler { - const fullConfig = - typeof config === 'string' ? { target: config } : { ...config }; + let fullConfig: ProxyConfig; + let credentialsPolicy: string; + if (typeof config === 'string') { + fullConfig = { target: config }; + credentialsPolicy = 'require'; + } else { + const { credentials, ...rest } = config as any; + fullConfig = rest; + credentialsPolicy = credentials ?? 'require'; + } + + const credentialsPolicyCandidates = [ + 'require', + 'forward', + 'dangerously-allow-unauthenticated', + ]; + if (!credentialsPolicyCandidates.includes(credentialsPolicy)) { + const valid = credentialsPolicyCandidates.map(c => `'${c}'`).join(', '); + throw new Error( + `Unknown credentials policy '${credentialsPolicy}' for proxy route '${route}'; expected one of ${valid}`, + ); + } + + if (credentialsPolicy === 'dangerously-allow-unauthenticated') { + httpRouterService?.addAuthPolicy({ + path: route, + allow: 'unauthenticated', + }); + } // Validate that target is a valid URL. const targetType = typeof fullConfig.target; @@ -144,6 +174,10 @@ export function buildMiddleware( ].map(h => h.toLocaleLowerCase()), ); + if (credentialsPolicy === 'forward') { + requestHeaderAllowList.add('authorization'); + } + // Use the custom middleware filter to do two things: // 1. Remove any headers not in the allow list to stop them being forwarded // 2. Only permit the allowed HTTP methods if configured @@ -194,13 +228,15 @@ export function buildMiddleware( return createProxyMiddleware(filter, fullConfig); } -function readProxyConfig(config: Config, logger: Logger): unknown { - const endpoints = config.getOptionalConfig('proxy.endpoints')?.get(); +function readProxyConfig(config: Config, logger: Logger): JsonObject { + const endpoints = config + .getOptionalConfig('proxy.endpoints') + ?.get(); if (endpoints) { return endpoints; } - const root = config.getOptionalConfig('proxy')?.get(); + const root = config.getOptionalConfig('proxy')?.get(); if (!root) { return {}; } @@ -220,25 +256,37 @@ function readProxyConfig(config: Config, logger: Logger): unknown { } /** - * Creates a new {@link https://expressjs.com/en/api.html#router | "express router"} that proxy each target configured under the `proxy` key of the config - * @example - * ```ts - * let router = await createRouter({logger, config, discovery}); - * ``` - * @config + * Creates a new + * {@link https://expressjs.com/en/api.html#router | "express router"} that + * proxies each target configured under the `proxy.endpoints` key of the config. + * + * @remarks + * + * Example configuration: + * * ```yaml * proxy: - * simple-example: http://simple.example.com:8080 # Opt 1 Simple URL String - * '/larger-example/v1': # Opt 2 `http-proxy-middleware` compatible object - * target: http://larger.example.com:8080/svc.v1 - * headers: - * Authorization: Bearer ${EXAMPLE_AUTH_TOKEN} - *``` + * endpoints: + * # Option 1: Simple URL String + * simple-example: http://simple.example.com:8080 + * # Option 2: `http-proxy-middleware` compatible object + * '/larger-example/v1': + * target: http://larger.example.com:8080/svc.v1 + * headers: + * Authorization: Bearer ${EXAMPLE_AUTH_TOKEN} + * ``` + * * @see https://backstage.io/docs/plugins/proxying * @public */ export async function createRouter( options: RouterOptions, +): Promise { + return createRouterInternal(options); +} + +export async function createRouterInternal( + options: RouterOptions & { httpRouterService?: HttpRouterService }, ): Promise { const router = Router(); let currentRouter = Router(); @@ -261,7 +309,13 @@ export async function createRouter( const { pathname: pathPrefix } = new URL(externalUrl); const proxyConfig = readProxyConfig(options.config, options.logger); - configureMiddlewares(proxyOptions, currentRouter, pathPrefix, proxyConfig); + configureMiddlewares( + proxyOptions, + currentRouter, + pathPrefix, + proxyConfig, + options.httpRouterService, + ); router.use((...args) => currentRouter(...args)); if (options.config.subscribe) { @@ -279,11 +333,13 @@ export async function createRouter( currentRouter, pathPrefix, newProxyConfig, + options.httpRouterService, ); } }); } + options.httpRouterService?.use(router); return router; } @@ -296,6 +352,7 @@ function configureMiddlewares( router: express.Router, pathPrefix: string, proxyConfig: any, + httpRouterService?: HttpRouterService, ) { Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { try { @@ -307,6 +364,7 @@ function configureMiddlewares( route, proxyRouteConfig, options.reviveConsumedRequestBodies, + httpRouterService, ), ); } catch (e) { diff --git a/yarn.lock b/yarn.lock index dbc233dd64..9eacb239f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6468,11 +6468,14 @@ __metadata: resolution: "@backstage/plugin-proxy-backend@workspace:plugins/proxy-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/http-proxy-middleware": ^1.0.0 "@types/supertest": ^2.0.8 @@ -6483,6 +6486,8 @@ __metadata: http-proxy-middleware: ^2.0.0 morgan: ^1.10.0 msw: ^1.0.0 + node-fetch: ^2.6.7 + portfinder: ^1.0.32 supertest: ^6.1.3 uuid: ^9.0.0 winston: ^3.2.1 From 5378a641debb190fbefc8bf744b0c4589e0fdbe4 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 6 May 2024 14:26:03 +0200 Subject: [PATCH 244/567] chore: support overwriting of action Signed-off-by: blam --- .github/workflows/api-breaking-changes.yml | 2 ++ .github/workflows/pr-review-comment-trigger.yaml | 1 + .github/workflows/scorecard.yml | 1 + .github/workflows/uffizzi-build.yml | 3 +++ 4 files changed, 7 insertions(+) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 7834fd195f..6db9720b49 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -47,6 +47,7 @@ jobs: name: preview-spec path: comment.md retention-days: 2 + overwrite: true - name: Upload PR Event as Artifact uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 @@ -54,3 +55,4 @@ jobs: name: preview-spec path: ${{ github.event_path }} retention-days: 2 + overwrite: true diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index 4d7d511cc8..5e7d71cbf4 100644 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ b/.github/workflows/pr-review-comment-trigger.yaml @@ -34,3 +34,4 @@ jobs: with: name: pr_number-${{ github.event.pull_request.number }} path: pr/ + overwrite: true diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 355ecf6727..bd47f315de 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -63,6 +63,7 @@ jobs: name: SARIF file path: results.sarif retention-days: 5 + overwrite: true # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 7ccf39489e..0fb5ea5b8a 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -103,12 +103,14 @@ jobs: name: preview-spec path: ./.github/uffizzi/k8s/manifests/manifests.rendered.yml retention-days: 2 + overwrite: true - name: Upload PR Event as Artifact uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: preview-spec path: ${{ github.event_path }} retention-days: 2 + overwrite: true delete-preview: name: Call for Preview Deletion @@ -127,3 +129,4 @@ jobs: name: preview-spec path: ${{ github.event_path }} retention-days: 2 + overwrite: true From e84c3c5e2dfb8148bf88729dc08e9de10d69c082 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 6 May 2024 08:48:01 -0400 Subject: [PATCH 245/567] adjust to just the raw constructor Signed-off-by: aramissennyeydd --- docs/features/software-catalog/extending-the-model.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index c058a12b12..ee363819cd 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -605,14 +605,9 @@ export const catalogModuleFoobarEntitiesProcessor = createBackendModule({ env.registerInit({ deps: { catalog: catalogProcessingExtensionPoint, - // my dependencies }, - async init({ catalog, ...deps }) { - catalog.addProcessor( - FoobarEntitiesProcessor.fromConfig(config, { - ...deps, - }), - ); + async init({ catalog }) { + catalog.addProcessor(new FoobarEntitiesProcessor()); }, }); }, From 9a7355f7600fdb25686b2fee7741fda2188dcbd5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 12:48:03 +0000 Subject: [PATCH 246/567] chore(deps): update dependency @types/lodash to v4.17.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f90b972ae7..a9d18ff80e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16116,9 +16116,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.0 - resolution: "@types/lodash@npm:4.17.0" - checksum: 3f98c0b67a93994cbc3403d4fa9dbaf52b0b6bb7f07a764d73875c2dcd5ef91222621bd5bcf8eee7b417a74d175c2f7191b9f595f8603956fd06f0674c0cba93 + version: 4.17.1 + resolution: "@types/lodash@npm:4.17.1" + checksum: 01984d5b44c09ef45258f8ac6d0cf926900624064722d51a020ba179e5d4a293da0068fb278d87dc695586afe7ebd3362ec57f5c0e7c4f6c1fab9d04a80e77f5 languageName: node linkType: hard From 56cfa19a2194b24952db8c861d38d7845c606527 Mon Sep 17 00:00:00 2001 From: AmbrishRamachandiran Date: Tue, 23 Apr 2024 14:51:16 +0530 Subject: [PATCH 247/567] Updated README Document of app custom theme Signed-off-by: AmbrishRamachandiran --- docs/getting-started/app-custom-theme.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index 8aadeaf19a..3fb0365ad0 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -28,7 +28,11 @@ export const myTheme = createUnifiedTheme({ }); ``` -> Note: we recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized. +:::note Note + +we recommend creating a `theme` folder in `packages/app/src` to place your theme file to keep things nicely organized. + +::: You can also create a theme from scratch that matches the `BackstageTheme` type exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). See the [Material UI docs on theming](https://material-ui.com/customization/theming/) for more information about how that can be done. @@ -504,7 +508,11 @@ You can add more icons, if the [default icons](https://github.com/backstage/back You might want to use this method if you have an icon you want to use in several locations. -Note: If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon` +:::note Note + +If the icon is not available as one of the default icons or one you've added then it will fall back to Material UI's `LanguageIcon` + +::: ## Custom Sidebar From f4fcce8cdc7ac6e95b2c0b8c6a52d935a3ddfe14 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 1 May 2024 15:06:45 +0200 Subject: [PATCH 248/567] backend -> backend-legacy, backend-next -> backend Signed-off-by: Patrik Oldsberg --- docs/auth/vmware-cloud/provider.md | 2 +- docs/backend-system/index.md | 2 +- docs/conf/reading.md | 2 +- docs/features/kubernetes/proxy.md | 2 - docs/features/search/how-to-guides.md | 2 +- .../software-catalog/external-integrations.md | 4 +- docs/plugins/new-backend-system.md | 2 +- package.json | 6 +- packages/app-next/README.md | 2 +- .../backend-dynamic-feature-service/README.md | 2 +- .../.eslintrc.js | 0 .../{backend-next => backend-legacy}/.snyk | 0 packages/backend-legacy/CHANGELOG.md | 6887 +++++++++++++++++ packages/backend-legacy/README.md | 61 + .../catalog-info.yaml | 4 +- packages/backend-legacy/knip-report.md | 27 + .../package.json | 74 +- .../prometheus.yml | 0 .../src/index.test.ts | 0 packages/backend-legacy/src/index.ts | 184 + .../src/metrics.ts | 0 .../plugins/DemoEventBasedEntityProvider.ts | 0 .../src/plugins/app.ts | 0 .../src/plugins/auth.ts | 0 .../src/plugins/catalog.ts | 0 .../src/plugins/devtools.ts | 0 .../src/plugins/events.ts | 0 .../src/plugins/healthcheck.ts | 0 .../src/plugins/kubernetes.ts | 0 .../src/plugins/permission.ts | 0 .../src/plugins/proxy.ts | 0 .../src/plugins/scaffolder.ts | 0 .../src/plugins/search.ts | 0 .../src/plugins/signals.ts | 0 .../src/plugins/techdocs.ts | 0 .../{backend => backend-legacy}/src/types.ts | 0 packages/backend-next/CHANGELOG.md | 2400 ------ packages/backend-next/README.md | 5 - packages/backend-next/knip-report.md | 12 - packages/backend-next/src/index.ts | 51 - packages/backend/CHANGELOG.md | 5391 ++----------- packages/backend/README.md | 16 +- packages/backend/knip-report.md | 31 +- packages/backend/package.json | 57 +- .../src/authModuleGithubProvider.ts | 0 packages/backend/src/index.ts | 195 +- plugins/auth-backend/api-report.md | 4 +- .../README.md | 18 +- .../search-backend-module-catalog/README.md | 4 +- yarn.lock | 89 +- 50 files changed, 7791 insertions(+), 7745 deletions(-) rename packages/{backend-next => backend-legacy}/.eslintrc.js (100%) rename packages/{backend-next => backend-legacy}/.snyk (100%) create mode 100644 packages/backend-legacy/CHANGELOG.md create mode 100644 packages/backend-legacy/README.md rename packages/{backend-next => backend-legacy}/catalog-info.yaml (68%) create mode 100644 packages/backend-legacy/knip-report.md rename packages/{backend-next => backend-legacy}/package.json (52%) rename packages/{backend => backend-legacy}/prometheus.yml (100%) rename packages/{backend => backend-legacy}/src/index.test.ts (100%) create mode 100644 packages/backend-legacy/src/index.ts rename packages/{backend => backend-legacy}/src/metrics.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/DemoEventBasedEntityProvider.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/app.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/auth.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/catalog.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/devtools.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/events.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/healthcheck.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/kubernetes.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/permission.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/proxy.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/scaffolder.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/search.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/signals.ts (100%) rename packages/{backend => backend-legacy}/src/plugins/techdocs.ts (100%) rename packages/{backend => backend-legacy}/src/types.ts (100%) delete mode 100644 packages/backend-next/CHANGELOG.md delete mode 100644 packages/backend-next/README.md delete mode 100644 packages/backend-next/knip-report.md delete mode 100644 packages/backend-next/src/index.ts rename packages/{backend-next => backend}/src/authModuleGithubProvider.ts (100%) diff --git a/docs/auth/vmware-cloud/provider.md b/docs/auth/vmware-cloud/provider.md index 8536eaea7e..0ff928ab7f 100644 --- a/docs/auth/vmware-cloud/provider.md +++ b/docs/auth/vmware-cloud/provider.md @@ -43,7 +43,7 @@ Cloud Console and within a Backstage app required to enable this capability. Apps using the [new backend system](../../backend-system/index.md), can enable the VMware Cloud provider with a small modification like: -```ts title="packages/backend-next/src/index.ts" +```ts title="packages/backend/src/index.ts" import { createBackend } from '@backstage/backend-defaults'; const backend = createBackend(); diff --git a/docs/backend-system/index.md b/docs/backend-system/index.md index 22ea4c6c59..b8cf5048be 100644 --- a/docs/backend-system/index.md +++ b/docs/backend-system/index.md @@ -10,4 +10,4 @@ description: The Backend System The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system. -You can find an example backend setup in [the `backend-next` package](https://github.com/backstage/backstage/tree/master/packages/backend-next). +You can find an example backend setup in [the `backend` package](https://github.com/backstage/backstage/tree/master/packages/backend). diff --git a/docs/conf/reading.md b/docs/conf/reading.md index 5f7218479a..f7d1edae86 100644 --- a/docs/conf/reading.md +++ b/docs/conf/reading.md @@ -145,7 +145,7 @@ from `@backstage/core-plugin-api`. In the old backend system plugins, the configuration is passed in via options from the main backend package. See for example -[packages/backend/src/plugins/auth.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/backend/src/plugins/auth.ts#L23). +[packages/backend-legacy/src/plugins/auth.ts](https://github.com/backstage/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/backend/src/plugins/auth.ts#L23). ### New Backend System diff --git a/docs/features/kubernetes/proxy.md b/docs/features/kubernetes/proxy.md index 5811ff8ec4..4ccc2d22b1 100644 --- a/docs/features/kubernetes/proxy.md +++ b/docs/features/kubernetes/proxy.md @@ -64,8 +64,6 @@ This feature assumes your backstage instance has enabled the [permissions framew A sample policy like: -[packages/backend/src/plugins/permissions.ts](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/permission.ts) - ```typescript import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; import { diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index e49bdb4fa5..6564fd0a44 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -393,7 +393,7 @@ There are other more specific search results layout components that also accept Recently, the Backstage maintainers [announced the new Backend System](https://backstage.io/blog/2023/02/15/backend-system-alpha). The search plugins are now migrated to support the new backend system. In this guide you will learn how to update your backend set up. -In "packages/backend-next/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]: +In "packages/backend/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]: ```ts import { searchPlugin } from '@backstage/plugin-search-backend/alpha'; diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index a1dcbabb99..6eaee2f80d 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -55,7 +55,7 @@ Some defining traits of entity providers: The recommended way of instantiating the catalog backend classes is to use the `CatalogBuilder`, as illustrated in the -[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts). +[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend-legacy/src/plugins/catalog.ts). We will create a new [`EntityProvider`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/provider.ts) subclass that can be added to this catalog builder. @@ -531,7 +531,7 @@ does so! The recommended way of instantiating the catalog backend classes is to use the `CatalogBuilder`, as illustrated in the -[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts). +[example backend here](https://github.com/backstage/backstage/blob/master/packages/backend-legacy/src/plugins/catalog.ts). We will create a new [`CatalogProcessor`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/processor.ts) subclass that can be added to this catalog builder. diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 08ed7e73c7..e8a2ca1b19 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -8,7 +8,7 @@ description: Details of the new backend system The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system. -You can find an example backend setup in [the backend-next package](https://github.com/backstage/backstage/tree/master/packages/backend-next). +You can find an example backend setup in [the backend package](https://github.com/backstage/backstage/tree/master/packages/backend). ## Overview diff --git a/package.json b/package.json index 768448402f..45bc0a2973 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "clean": "backstage-cli repo clean", "create-plugin": "echo \"use 'yarn new' instead\"", "dev": "yarn workspaces foreach -A --include example-backend --include example-app --parallel -v -i run start", - "dev:next": "yarn workspaces foreach -A --include example-backend-next --include example-app-next --parallel -v -i run start", + "dev:next": "yarn workspaces foreach -A --include example-backend --include example-app-next --parallel -v -i run start", "docker-build": "yarn tsc && yarn workspace example-backend build && yarn workspace example-backend build-image", "fix": "backstage-cli repo fix", "postinstall": "husky || true", @@ -41,8 +41,8 @@ "snyk:test:package": "yarn snyk:test --include", "start": "yarn workspace example-app start", "start-backend": "yarn workspace example-backend start", - "start-backend:next": "yarn workspace example-backend-next start", - "start:lighthouse": "yarn workspaces foreach -A --include example-backend-next --include example-app --parallel -v -i run start", + "start-backend:legacy": "yarn workspace example-backend-legacy start", + "start:lighthouse": "yarn workspaces foreach -A --include example-backend --include example-app --parallel -v -i run start", "start:microsite": "cd microsite/ && yarn start", "start:next": "yarn workspace example-app-next start", "storybook": "yarn --cwd storybook && yarn --cwd storybook start", diff --git a/packages/app-next/README.md b/packages/app-next/README.md index 360e5d5545..203aaa9f79 100644 --- a/packages/app-next/README.md +++ b/packages/app-next/README.md @@ -4,4 +4,4 @@ This package is an example of a Backstage application using the [new frontend](. To play with it, open a terminal and run the command: `yarn start` -**NOTE:** Don't forget to open a second terminal and to launch the backend or [backend-next](../../docs/backend-system/index.md) there, using `yarn start`! The frontend requires a backend to connect to. +**NOTE:** Don't forget to open a second terminal and to launch the backend there, using `yarn start`! The frontend requires a backend to connect to. diff --git a/packages/backend-dynamic-feature-service/README.md b/packages/backend-dynamic-feature-service/README.md index 211148ee10..a2f29b3d8f 100644 --- a/packages/backend-dynamic-feature-service/README.md +++ b/packages/backend-dynamic-feature-service/README.md @@ -10,7 +10,7 @@ In order to test the dynamic backend plugins feature provided by this package, e The dynamic plugin manager is a service that scans a configured root directory (`dynamicPlugins.rootDirectory` in the app config) for dynamic plugin packages, and loads them dynamically. -In the `backend-next` application, it can be enabled by adding the `backend-dynamic-feature-service` as a dependency in the `package.json` and the following lines in the `src/index.ts` file: +In the `backend` application, it can be enabled by adding the `backend-dynamic-feature-service` as a dependency in the `package.json` and the following lines in the `src/index.ts` file: ```ts const backend = createBackend(); diff --git a/packages/backend-next/.eslintrc.js b/packages/backend-legacy/.eslintrc.js similarity index 100% rename from packages/backend-next/.eslintrc.js rename to packages/backend-legacy/.eslintrc.js diff --git a/packages/backend-next/.snyk b/packages/backend-legacy/.snyk similarity index 100% rename from packages/backend-next/.snyk rename to packages/backend-legacy/.snyk diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md new file mode 100644 index 0000000000..f6d199c6df --- /dev/null +++ b/packages/backend-legacy/CHANGELOG.md @@ -0,0 +1,6887 @@ +# example-backend-legacy + +## 0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + - @backstage/plugin-scaffolder-backend@1.22.5-next.1 + - example-app@0.2.97-next.1 + - @backstage/plugin-search-backend@1.5.8-next.1 + - @backstage/plugin-app-backend@0.3.66-next.1 + - @backstage/plugin-kubernetes-backend@0.17.1-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-devtools-backend@0.3.4-next.1 + - @backstage/plugin-events-backend@0.3.5-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-permission-backend@0.5.42-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-proxy-backend@0.4.16-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.24-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.27-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-signals-backend@0.1.4-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/plugin-techdocs-backend@1.10.5-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + +## 0.2.98-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/plugin-scaffolder-backend@1.22.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/plugin-search-backend@1.5.8-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.23-next.0 + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - example-app@0.2.97-next.0 + - @backstage/plugin-app-backend@0.3.66-next.0 + - @backstage/plugin-kubernetes-backend@0.17.1-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + - @backstage/plugin-techdocs-backend@1.10.5-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-devtools-backend@0.3.4-next.0 + - @backstage/plugin-events-backend@0.3.5-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-backend@0.5.42-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-proxy-backend@0.4.16-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.27-next.0 + - @backstage/plugin-signals-backend@0.1.4-next.0 + - @backstage/plugin-signals-node@0.1.4-next.0 + +## 0.2.97 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-pg@0.5.26 + - @backstage/plugin-badges-backend@0.4.0 + - @backstage/plugin-kubernetes-backend@0.17.0 + - @backstage/backend-common@0.21.7 + - @backstage/plugin-azure-devops-backend@0.6.4 + - @backstage/plugin-techdocs-backend@1.10.4 + - @backstage/plugin-permission-node@0.7.28 + - @backstage/plugin-auth-backend@0.22.4 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.3 + - @backstage/plugin-catalog-backend@1.21.1 + - @backstage/plugin-events-backend@0.3.4 + - @backstage/plugin-tech-insights-node@0.6.0 + - @backstage/plugin-search-backend@1.5.7 + - @backstage/plugin-todo-backend@0.3.16 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.49 + - @backstage/plugin-search-backend-module-techdocs@0.1.22 + - @backstage/plugin-search-backend-module-explore@0.1.21 + - @backstage/plugin-entity-feedback-backend@0.2.14 + - @backstage/plugin-code-coverage-backend@0.2.31 + - @backstage/plugin-tech-insights-backend@0.5.31 + - @backstage/plugin-search-backend-node@1.2.21 + - @backstage/plugin-lighthouse-backend@0.4.10 + - @backstage/plugin-permission-backend@0.5.41 + - @backstage/plugin-devtools-backend@0.3.3 + - @backstage/plugin-linguist-backend@0.5.15 + - @backstage/plugin-playlist-backend@0.3.21 + - @backstage/plugin-explore-backend@0.0.27 + - @backstage/plugin-jenkins-backend@0.4.4 + - @backstage/backend-tasks@0.5.22 + - @backstage/plugin-kafka-backend@0.3.15 + - @backstage/plugin-nomad-backend@0.1.19 + - @backstage/plugin-adr-backend@0.4.14 + - @backstage/plugin-app-backend@0.3.65 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/plugin-signals-backend@0.1.3 + - @backstage/plugin-proxy-backend@0.4.15 + - @backstage/plugin-scaffolder-backend@1.22.4 + - @backstage/catalog-client@1.6.4 + - @backstage/integration@1.10.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.0 + - example-app@0.2.96 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4 + - @backstage/plugin-events-node@0.3.3 + - @backstage/plugin-rollbar-backend@0.1.62 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.18 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.34 + - @backstage/plugin-search-backend-module-catalog@0.1.22 + - @backstage/plugin-signals-node@0.1.3 + - @backstage/plugin-catalog-node@1.11.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15 + - @backstage/plugin-permission-common@0.7.13 + +## 0.2.97-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.17.0-next.1 + - @backstage/backend-common@0.21.7-next.1 + - @backstage/plugin-azure-devops-backend@0.6.4-next.1 + - @backstage/plugin-techdocs-backend@1.10.4-next.1 + - @backstage/plugin-events-backend@0.3.4-next.1 + - @backstage/plugin-auth-backend@0.22.4-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/plugin-proxy-backend@0.4.15-next.1 + - @backstage/plugin-scaffolder-backend@1.22.4-next.1 + - @backstage/plugin-catalog-backend@1.21.1-next.1 + - @backstage/catalog-client@1.6.4-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.3-next.1 + - @backstage/plugin-app-backend@0.3.65-next.1 + - @backstage/backend-tasks@0.5.22-next.1 + - @backstage/plugin-adr-backend@0.4.14-next.1 + - @backstage/plugin-badges-backend@0.3.14-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4-next.1 + - @backstage/plugin-code-coverage-backend@0.2.31-next.1 + - @backstage/plugin-devtools-backend@0.3.3-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.14-next.1 + - @backstage/plugin-events-node@0.3.3-next.1 + - @backstage/plugin-explore-backend@0.0.27-next.1 + - @backstage/plugin-jenkins-backend@0.4.4-next.1 + - @backstage/plugin-kafka-backend@0.3.15-next.1 + - @backstage/plugin-lighthouse-backend@0.4.10-next.1 + - @backstage/plugin-linguist-backend@0.5.15-next.1 + - @backstage/plugin-nomad-backend@0.1.19-next.1 + - @backstage/plugin-permission-backend@0.5.41-next.1 + - @backstage/plugin-permission-node@0.7.28-next.1 + - @backstage/plugin-playlist-backend@0.3.21-next.1 + - @backstage/plugin-rollbar-backend@0.1.62-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.18-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.34-next.1 + - @backstage/plugin-search-backend@1.5.7-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.22-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.20-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.21-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.26-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.22-next.1 + - @backstage/plugin-search-backend-node@1.2.21-next.1 + - @backstage/plugin-signals-backend@0.1.3-next.1 + - @backstage/plugin-signals-node@0.1.3-next.1 + - @backstage/plugin-tech-insights-backend@0.5.31-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.49-next.1 + - @backstage/plugin-tech-insights-node@0.5.3-next.1 + - @backstage/plugin-todo-backend@0.3.16-next.1 + - example-app@0.2.96-next.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15-next.1 + - @backstage/plugin-catalog-node@1.11.1-next.1 + - @backstage/plugin-permission-common@0.7.13 + +## 0.2.97-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.4-next.0 + - @backstage/plugin-catalog-backend@1.21.1-next.0 + - @backstage/plugin-kubernetes-backend@0.16.4-next.0 + - @backstage/plugin-signals-backend@0.1.3-next.0 + - @backstage/backend-common@0.21.7-next.0 + - @backstage/integration@1.10.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.22-next.0 + - @backstage/plugin-scaffolder-backend@1.22.4-next.0 + - @backstage/plugin-app-backend@0.3.65-next.0 + - example-app@0.2.96-next.0 + - @backstage/backend-tasks@0.5.22-next.0 + - @backstage/catalog-client@1.6.3 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/plugin-adr-backend@0.4.14-next.0 + - @backstage/plugin-auth-backend@0.22.4-next.0 + - @backstage/plugin-auth-node@0.4.12-next.0 + - @backstage/plugin-azure-devops-backend@0.6.4-next.0 + - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-badges-backend@0.3.14-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.11.1-next.0 + - @backstage/plugin-code-coverage-backend@0.2.31-next.0 + - @backstage/plugin-devtools-backend@0.3.3-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.14-next.0 + - @backstage/plugin-events-backend@0.3.3-next.0 + - @backstage/plugin-events-node@0.3.3-next.0 + - @backstage/plugin-explore-backend@0.0.27-next.0 + - @backstage/plugin-jenkins-backend@0.4.4-next.0 + - @backstage/plugin-kafka-backend@0.3.15-next.0 + - @backstage/plugin-lighthouse-backend@0.4.10-next.0 + - @backstage/plugin-linguist-backend@0.5.15-next.0 + - @backstage/plugin-nomad-backend@0.1.19-next.0 + - @backstage/plugin-permission-backend@0.5.41-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.28-next.0 + - @backstage/plugin-playlist-backend@0.3.21-next.0 + - @backstage/plugin-proxy-backend@0.4.15-next.0 + - @backstage/plugin-rollbar-backend@0.1.62-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.18-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.3-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.34-next.0 + - @backstage/plugin-search-backend@1.5.7-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.22-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.20-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.21-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.26-next.0 + - @backstage/plugin-search-backend-node@1.2.21-next.0 + - @backstage/plugin-signals-node@0.1.3-next.0 + - @backstage/plugin-tech-insights-backend@0.5.31-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.49-next.0 + - @backstage/plugin-tech-insights-node@0.5.3-next.0 + - @backstage/plugin-todo-backend@0.3.16-next.0 + +## 0.2.96 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.21.0 + - @backstage/plugin-catalog-node@1.11.0 + - @backstage/plugin-kubernetes-backend@0.16.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.3 + - @backstage/plugin-permission-backend@0.5.40 + - @backstage/plugin-proxy-backend@0.4.14 + - @backstage/plugin-scaffolder-backend@1.22.3 + - @backstage/catalog-client@1.6.3 + - @backstage/plugin-jenkins-backend@0.4.3 + - @backstage/plugin-auth-backend@0.22.3 + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-common@0.21.6 + - @backstage/plugin-azure-devops-backend@0.6.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.14 + - @backstage/plugin-lighthouse-backend@0.4.9 + - @backstage/plugin-linguist-backend@0.5.14 + - @backstage/plugin-search-backend-module-catalog@0.1.21 + - @backstage/plugin-search-backend-module-techdocs@0.1.21 + - @backstage/plugin-todo-backend@0.3.15 + - example-app@0.2.95 + - @backstage/plugin-app-backend@0.3.64 + - @backstage/plugin-adr-backend@0.4.13 + - @backstage/plugin-badges-backend@0.3.13 + - @backstage/plugin-code-coverage-backend@0.2.30 + - @backstage/plugin-entity-feedback-backend@0.2.13 + - @backstage/plugin-playlist-backend@0.3.20 + - @backstage/plugin-tech-insights-backend@0.5.30 + - @backstage/plugin-techdocs-backend@1.10.3 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.2 + - @backstage/plugin-permission-node@0.7.27 + - @backstage/plugin-signals-backend@0.1.2 + - @backstage/plugin-signals-node@0.1.2 + - @backstage/backend-tasks@0.5.21 + - @backstage/plugin-devtools-backend@0.3.2 + - @backstage/plugin-events-backend@0.3.2 + - @backstage/plugin-events-node@0.3.2 + - @backstage/plugin-explore-backend@0.0.26 + - @backstage/plugin-kafka-backend@0.3.14 + - @backstage/plugin-nomad-backend@0.1.18 + - @backstage/plugin-rollbar-backend@0.1.61 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.17 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.33 + - @backstage/plugin-search-backend@1.5.6 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.19 + - @backstage/plugin-search-backend-module-explore@0.1.20 + - @backstage/plugin-search-backend-module-pg@0.5.25 + - @backstage/plugin-search-backend-node@1.2.20 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.48 + - @backstage/plugin-tech-insights-node@0.5.2 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/integration@1.9.1 + - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-permission-common@0.7.13 + +## 0.2.95 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.20.0 + - @backstage/plugin-catalog-node@1.10.0 + - @backstage/plugin-kubernetes-backend@0.16.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.2 + - @backstage/plugin-permission-backend@0.5.39 + - @backstage/catalog-client@1.6.2 + - @backstage/backend-common@0.21.5 + - @backstage/plugin-auth-backend@0.22.2 + - @backstage/plugin-azure-devops-backend@0.6.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.13 + - @backstage/plugin-jenkins-backend@0.4.2 + - @backstage/plugin-lighthouse-backend@0.4.8 + - @backstage/plugin-linguist-backend@0.5.13 + - @backstage/plugin-scaffolder-backend@1.22.2 + - @backstage/plugin-search-backend-module-catalog@0.1.20 + - @backstage/plugin-search-backend-module-techdocs@0.1.20 + - @backstage/plugin-todo-backend@0.3.14 + - example-app@0.2.94 + - @backstage/plugin-app-backend@0.3.63 + - @backstage/plugin-adr-backend@0.4.12 + - @backstage/plugin-auth-node@0.4.10 + - @backstage/plugin-badges-backend@0.3.12 + - @backstage/plugin-code-coverage-backend@0.2.29 + - @backstage/plugin-entity-feedback-backend@0.2.12 + - @backstage/plugin-playlist-backend@0.3.19 + - @backstage/plugin-tech-insights-backend@0.5.29 + - @backstage/plugin-techdocs-backend@1.10.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.1 + - @backstage/backend-tasks@0.5.20 + - @backstage/plugin-devtools-backend@0.3.1 + - @backstage/plugin-events-backend@0.3.1 + - @backstage/plugin-events-node@0.3.1 + - @backstage/plugin-explore-backend@0.0.25 + - @backstage/plugin-kafka-backend@0.3.13 + - @backstage/plugin-nomad-backend@0.1.17 + - @backstage/plugin-permission-node@0.7.26 + - @backstage/plugin-proxy-backend@0.4.13 + - @backstage/plugin-rollbar-backend@0.1.60 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.16 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.32 + - @backstage/plugin-search-backend@1.5.5 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.18 + - @backstage/plugin-search-backend-module-explore@0.1.19 + - @backstage/plugin-search-backend-module-pg@0.5.24 + - @backstage/plugin-search-backend-node@1.2.19 + - @backstage/plugin-signals-backend@0.1.1 + - @backstage/plugin-signals-node@0.1.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.47 + - @backstage/plugin-tech-insights-node@0.5.1 + - @backstage/catalog-model@1.4.5 + - @backstage/config@1.2.0 + - @backstage/integration@1.9.1 + - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-permission-common@0.7.13 + +## 0.2.94 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.19.0 + - @backstage/plugin-catalog-node@1.9.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.1 + - @backstage/plugin-permission-backend@0.5.38 + - @backstage/plugin-auth-backend@0.22.1 + - @backstage/plugin-azure-devops-backend@0.6.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.12 + - @backstage/plugin-jenkins-backend@0.4.1 + - @backstage/plugin-kubernetes-backend@0.16.1 + - @backstage/plugin-lighthouse-backend@0.4.7 + - @backstage/plugin-linguist-backend@0.5.12 + - @backstage/plugin-scaffolder-backend@1.22.1 + - @backstage/plugin-search-backend-module-catalog@0.1.19 + - @backstage/plugin-search-backend-module-techdocs@0.1.19 + - @backstage/plugin-todo-backend@0.3.13 + - @backstage/plugin-techdocs-backend@1.10.1 + +## 0.2.93 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-backend@0.3.0 + - @backstage/plugin-events-node@0.3.0 + - @backstage/plugin-scaffolder-backend@1.22.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.31 + - @backstage/plugin-linguist-backend@0.5.11 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.0 + - @backstage/plugin-catalog-backend@1.18.0 + - @backstage/plugin-code-coverage-backend@0.2.28 + - @backstage/plugin-devtools-backend@0.3.0 + - @backstage/plugin-jenkins-backend@0.4.0 + - @backstage/plugin-search-backend@1.5.4 + - @backstage/backend-common@0.21.4 + - @backstage/integration@1.9.1 + - @backstage/plugin-auth-node@0.4.9 + - @backstage/plugin-lighthouse-backend@0.4.6 + - @backstage/config@1.2.0 + - @backstage/plugin-azure-devops-backend@0.6.0 + - @backstage/plugin-permission-backend@0.5.37 + - @backstage/plugin-signals-backend@0.1.0 + - @backstage/plugin-nomad-backend@0.1.16 + - @backstage/plugin-entity-feedback-backend@0.2.11 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.17 + - @backstage/plugin-search-backend-module-pg@0.5.23 + - @backstage/plugin-signals-node@0.1.0 + - @backstage/plugin-playlist-backend@0.3.18 + - @backstage/plugin-auth-backend@0.22.0 + - @backstage/plugin-techdocs-backend@1.10.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.15 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-backend-module-techdocs@0.1.18 + - @backstage/plugin-search-backend-module-catalog@0.1.18 + - @backstage/plugin-search-backend-module-explore@0.1.18 + - @backstage/plugin-catalog-node@1.8.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.46 + - @backstage/catalog-client@1.6.1 + - @backstage/plugin-kubernetes-backend@0.16.0 + - @backstage/plugin-adr-backend@0.4.11 + - @backstage/plugin-proxy-backend@0.4.12 + - @backstage/backend-tasks@0.5.19 + - @backstage/plugin-search-backend-node@1.2.18 + - @backstage/plugin-tech-insights-backend@0.5.28 + - @backstage/plugin-app-backend@0.3.62 + - @backstage/plugin-permission-node@0.7.25 + - @backstage/plugin-todo-backend@0.3.12 + - @backstage/plugin-tech-insights-node@0.5.0 + - @backstage/plugin-badges-backend@0.3.11 + - example-app@0.2.93 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11 + - @backstage/plugin-explore-backend@0.0.24 + - @backstage/plugin-rollbar-backend@0.1.59 + - @backstage/plugin-kafka-backend@0.3.12 + - @backstage/catalog-model@1.4.5 + - @backstage/plugin-azure-sites-common@0.1.3 + +## 0.2.93-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.22.0-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.31-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.11-next.2 + - @backstage/plugin-catalog-backend@1.18.0-next.2 + - @backstage/plugin-code-coverage-backend@0.2.28-next.2 + - @backstage/plugin-devtools-backend@0.3.0-next.2 + - @backstage/plugin-jenkins-backend@0.4.0-next.2 + - @backstage/plugin-search-backend@1.5.4-next.2 + - @backstage/integration@1.9.1-next.2 + - @backstage/plugin-techdocs-backend@1.10.0-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.0-next.2 + - @backstage/plugin-signals-node@0.1.0-next.2 + - @backstage/catalog-client@1.6.1-next.1 + - @backstage/plugin-linguist-backend@0.5.11-next.2 + - @backstage/plugin-kubernetes-backend@0.16.0-next.2 + - @backstage/plugin-todo-backend@0.3.12-next.2 + - @backstage/plugin-signals-backend@0.1.0-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.15-next.2 + - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-adr-backend@0.4.11-next.2 + - @backstage/plugin-azure-devops-backend@0.6.0-next.2 + - example-app@0.2.93-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.18-next.2 + - @backstage/plugin-auth-backend@0.22.0-next.2 + - @backstage/plugin-app-backend@0.3.62-next.2 + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/plugin-badges-backend@0.3.11-next.2 + - @backstage/plugin-catalog-node@1.8.0-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.11-next.2 + - @backstage/plugin-lighthouse-backend@0.4.6-next.2 + - @backstage/plugin-playlist-backend@0.3.18-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.18-next.2 + - @backstage/plugin-tech-insights-backend@0.5.28-next.2 + - @backstage/backend-tasks@0.5.19-next.2 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.2.0-next.1 + - @backstage/plugin-azure-sites-common@0.1.3-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11-next.2 + - @backstage/plugin-events-backend@0.3.0-next.2 + - @backstage/plugin-events-node@0.3.0-next.2 + - @backstage/plugin-explore-backend@0.0.24-next.2 + - @backstage/plugin-kafka-backend@0.3.12-next.2 + - @backstage/plugin-nomad-backend@0.1.16-next.2 + - @backstage/plugin-permission-backend@0.5.37-next.2 + - @backstage/plugin-permission-common@0.7.13-next.1 + - @backstage/plugin-permission-node@0.7.25-next.2 + - @backstage/plugin-proxy-backend@0.4.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.59-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.17-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.18-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.23-next.2 + - @backstage/plugin-search-backend-node@1.2.18-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.46-next.2 + - @backstage/plugin-tech-insights-node@0.5.0-next.2 + +## 0.2.93-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend@1.22.0-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.17-next.1 + - @backstage/plugin-app-backend@0.3.62-next.1 + - @backstage/plugin-signals-backend@0.1.0-next.1 + - @backstage/plugin-signals-node@0.1.0-next.1 + - @backstage/plugin-azure-devops-backend@0.6.0-next.1 + - @backstage/plugin-kubernetes-backend@0.16.0-next.1 + - example-app@0.2.93-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-tasks@0.5.19-next.1 + - @backstage/integration@1.9.1-next.1 + - @backstage/plugin-adr-backend@0.4.11-next.1 + - @backstage/plugin-auth-backend@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/plugin-badges-backend@0.3.11-next.1 + - @backstage/plugin-catalog-backend@1.18.0-next.1 + - @backstage/plugin-code-coverage-backend@0.2.28-next.1 + - @backstage/plugin-devtools-backend@0.3.0-next.1 + - @backstage/plugin-events-backend@0.3.0-next.1 + - @backstage/plugin-explore-backend@0.0.24-next.1 + - @backstage/plugin-jenkins-backend@0.4.0-next.1 + - @backstage/plugin-kafka-backend@0.3.12-next.1 + - @backstage/plugin-lighthouse-backend@0.4.6-next.1 + - @backstage/plugin-linguist-backend@0.5.11-next.1 + - @backstage/plugin-nomad-backend@0.1.16-next.1 + - @backstage/plugin-permission-backend@0.5.37-next.1 + - @backstage/plugin-permission-common@0.7.13-next.1 + - @backstage/plugin-permission-node@0.7.25-next.1 + - @backstage/plugin-playlist-backend@0.3.18-next.1 + - @backstage/plugin-proxy-backend@0.4.12-next.1 + - @backstage/plugin-rollbar-backend@0.1.59-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.15-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.17-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.31-next.1 + - @backstage/plugin-search-backend@1.5.4-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.18-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.18-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.23-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.18-next.1 + - @backstage/plugin-search-backend-node@1.2.18-next.1 + - @backstage/plugin-tech-insights-backend@0.5.28-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.46-next.1 + - @backstage/plugin-tech-insights-node@0.5.0-next.1 + - @backstage/plugin-techdocs-backend@1.9.7-next.1 + - @backstage/plugin-todo-backend@0.3.12-next.1 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/plugin-azure-sites-common@0.1.3-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.11-next.1 + - @backstage/plugin-catalog-node@1.8.0-next.1 + - @backstage/plugin-events-node@0.3.0-next.1 + +## 0.2.93-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-backend@0.3.0-next.0 + - @backstage/plugin-events-node@0.3.0-next.0 + - @backstage/plugin-linguist-backend@0.5.10-next.0 + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/plugin-lighthouse-backend@0.4.5-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.16-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.22-next.0 + - @backstage/plugin-playlist-backend@0.3.17-next.0 + - @backstage/plugin-code-coverage-backend@0.2.27-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.10-next.0 + - @backstage/plugin-catalog-backend@1.18.0-next.0 + - @backstage/plugin-auth-backend@0.22.0-next.0 + - @backstage/plugin-jenkins-backend@0.4.0-next.0 + - @backstage/plugin-azure-devops-backend@0.6.0-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.14-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.30-next.0 + - @backstage/plugin-scaffolder-backend@1.22.0-next.0 + - @backstage/plugin-permission-common@0.7.13-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.17-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.17-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.17-next.0 + - @backstage/plugin-catalog-node@1.8.0-next.0 + - @backstage/plugin-kubernetes-backend@0.16.0-next.0 + - @backstage/plugin-adr-backend@0.4.10-next.0 + - @backstage/plugin-proxy-backend@0.4.11-next.0 + - @backstage/backend-tasks@0.5.18-next.0 + - @backstage/plugin-search-backend-node@1.2.17-next.0 + - @backstage/plugin-signals-backend@0.0.4-next.0 + - @backstage/plugin-signals-node@0.0.4-next.0 + - @backstage/plugin-tech-insights-backend@0.5.27-next.0 + - @backstage/plugin-search-backend@1.5.3-next.0 + - @backstage/plugin-devtools-backend@0.3.0-next.0 + - @backstage/plugin-permission-node@0.7.24-next.0 + - @backstage/plugin-tech-insights-node@0.5.0-next.0 + - @backstage/plugin-badges-backend@0.3.10-next.0 + - @backstage/plugin-permission-backend@0.5.36-next.0 + - @backstage/plugin-app-backend@0.3.61-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.10-next.0 + - @backstage/plugin-explore-backend@0.0.23-next.0 + - @backstage/plugin-rollbar-backend@0.1.58-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.45-next.0 + - @backstage/plugin-techdocs-backend@1.9.6-next.0 + - @backstage/plugin-kafka-backend@0.3.11-next.0 + - @backstage/plugin-nomad-backend@0.1.15-next.0 + - @backstage/plugin-todo-backend@0.3.11-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.10-next.0 + - example-app@0.2.93-next.0 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/integration@1.9.1-next.0 + - @backstage/plugin-azure-sites-common@0.1.3-next.0 + +## 0.2.92 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.27 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7 + - @backstage/plugin-scaffolder-backend@1.21.0 + - @backstage/plugin-badges-backend@0.3.7 + - @backstage/plugin-azure-devops-backend@0.5.2 + - @backstage/plugin-explore-backend@0.0.20 + - @backstage/plugin-auth-backend@0.21.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/plugin-entity-feedback-backend@0.2.7 + - @backstage/plugin-lighthouse-backend@0.4.2 + - @backstage/plugin-devtools-backend@0.2.7 + - @backstage/plugin-linguist-backend@0.5.7 + - @backstage/plugin-adr-backend@0.4.7 + - @backstage/plugin-kubernetes-backend@0.15.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.13 + - @backstage/plugin-signals-backend@0.0.1 + - @backstage/plugin-signals-node@0.0.1 + - @backstage/plugin-tech-insights-backend@0.5.24 + - @backstage/plugin-tech-insights-node@0.4.16 + - @backstage/plugin-search-backend-module-techdocs@0.1.14 + - @backstage/plugin-search-backend-module-catalog@0.1.14 + - @backstage/plugin-search-backend-module-explore@0.1.14 + - @backstage/plugin-code-coverage-backend@0.2.24 + - @backstage/plugin-playlist-backend@0.3.14 + - @backstage/plugin-catalog-backend@1.17.0 + - @backstage/plugin-jenkins-backend@0.3.4 + - @backstage/plugin-rollbar-backend@0.1.55 + - @backstage/backend-tasks@0.5.15 + - @backstage/plugin-events-backend@0.2.19 + - @backstage/plugin-nomad-backend@0.1.12 + - @backstage/plugin-app-backend@0.3.58 + - @backstage/catalog-model@1.4.4 + - @backstage/integration@1.9.0 + - @backstage/catalog-client@1.6.0 + - @backstage/plugin-search-backend@1.5.0 + - @backstage/plugin-todo-backend@0.3.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7 + - @backstage/plugin-techdocs-backend@1.9.3 + - @backstage/plugin-catalog-node@1.7.0 + - @backstage/plugin-azure-sites-common@0.1.2 + - example-app@0.2.92 + - @backstage/plugin-kafka-backend@0.3.8 + - @backstage/plugin-permission-backend@0.5.33 + - @backstage/plugin-permission-node@0.7.21 + - @backstage/plugin-proxy-backend@0.4.8 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.13 + - @backstage/plugin-search-backend-module-pg@0.5.19 + - @backstage/plugin-search-backend-node@1.2.14 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.19 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.92-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/plugin-badges-backend@0.3.7-next.3 + - @backstage/plugin-kubernetes-backend@0.15.0-next.3 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.13-next.3 + - @backstage/integration@1.9.0-next.1 + - @backstage/backend-tasks@0.5.15-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.3 + - @backstage/plugin-signals-backend@0.0.1-next.3 + - @backstage/plugin-signals-node@0.0.1-next.3 + - @backstage/plugin-catalog-backend@1.17.0-next.3 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.3 + - @backstage/plugin-app-backend@0.3.58-next.3 + - @backstage/plugin-auth-backend@0.21.0-next.3 + - @backstage/plugin-catalog-node@1.6.2-next.3 + - @backstage/plugin-adr-backend@0.4.7-next.3 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/plugin-azure-devops-backend@0.5.2-next.3 + - @backstage/plugin-code-coverage-backend@0.2.24-next.3 + - @backstage/plugin-devtools-backend@0.2.7-next.3 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.3 + - @backstage/plugin-events-backend@0.2.19-next.3 + - @backstage/plugin-explore-backend@0.0.20-next.3 + - @backstage/plugin-jenkins-backend@0.3.4-next.3 + - @backstage/plugin-kafka-backend@0.3.8-next.3 + - @backstage/plugin-lighthouse-backend@0.4.2-next.3 + - @backstage/plugin-linguist-backend@0.5.7-next.3 + - @backstage/plugin-nomad-backend@0.1.12-next.3 + - @backstage/plugin-permission-backend@0.5.33-next.3 + - @backstage/plugin-permission-node@0.7.21-next.3 + - @backstage/plugin-playlist-backend@0.3.14-next.3 + - @backstage/plugin-proxy-backend@0.4.8-next.3 + - @backstage/plugin-rollbar-backend@0.1.55-next.3 + - @backstage/plugin-scaffolder-backend@1.21.0-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.3 + - @backstage/plugin-search-backend@1.5.0-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.3 + - @backstage/plugin-search-backend-module-pg@0.5.19-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.3 + - @backstage/plugin-search-backend-node@1.2.14-next.3 + - @backstage/plugin-tech-insights-backend@0.5.24-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.3 + - @backstage/plugin-tech-insights-node@0.4.16-next.3 + - @backstage/plugin-techdocs-backend@1.9.3-next.3 + - @backstage/plugin-todo-backend@0.3.8-next.3 + - example-app@0.2.92-next.3 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.2-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.3 + - @backstage/plugin-events-node@0.2.19-next.3 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.92-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.21.0-next.2 + - @backstage/plugin-auth-backend@0.21.0-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.2 + - @backstage/backend-common@0.21.0-next.2 + - @backstage/plugin-signals-backend@0.0.1-next.2 + - @backstage/plugin-signals-node@0.0.1-next.2 + - @backstage/plugin-kubernetes-backend@0.15.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.24-next.2 + - @backstage/plugin-tech-insights-node@0.4.16-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.2 + - @backstage/plugin-code-coverage-backend@0.2.24-next.2 + - @backstage/plugin-azure-devops-backend@0.5.2-next.2 + - @backstage/plugin-lighthouse-backend@0.4.2-next.2 + - @backstage/plugin-devtools-backend@0.2.7-next.2 + - @backstage/plugin-linguist-backend@0.5.7-next.2 + - @backstage/plugin-playlist-backend@0.3.14-next.2 + - @backstage/plugin-catalog-backend@1.17.0-next.2 + - @backstage/plugin-explore-backend@0.0.20-next.2 + - @backstage/plugin-jenkins-backend@0.3.4-next.2 + - @backstage/plugin-rollbar-backend@0.1.55-next.2 + - @backstage/backend-tasks@0.5.15-next.2 + - @backstage/plugin-badges-backend@0.3.7-next.2 + - @backstage/plugin-events-backend@0.2.19-next.2 + - @backstage/plugin-nomad-backend@0.1.12-next.2 + - @backstage/plugin-adr-backend@0.4.7-next.2 + - @backstage/plugin-app-backend@0.3.58-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - example-app@0.2.92-next.2 + - @backstage/plugin-todo-backend@0.3.8-next.2 + - @backstage/plugin-kafka-backend@0.3.8-next.2 + - @backstage/plugin-permission-backend@0.5.33-next.2 + - @backstage/plugin-permission-node@0.7.21-next.2 + - @backstage/plugin-proxy-backend@0.4.8-next.2 + - @backstage/plugin-search-backend@1.5.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.19-next.2 + - @backstage/plugin-search-backend-node@1.2.14-next.2 + - @backstage/plugin-techdocs-backend@1.9.3-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.2 + - @backstage/plugin-catalog-node@1.6.2-next.2 + - @backstage/plugin-events-node@0.2.19-next.2 + - @backstage/config@1.1.1 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/integration@1.9.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.2-next.0 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.92-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.21.0-next.1 + - @backstage/plugin-azure-devops-backend@0.5.2-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/plugin-catalog-backend@1.17.0-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/plugin-auth-backend@0.20.4-next.1 + - @backstage/integration@1.9.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.2-next.0 + - example-app@0.2.92-next.1 + - @backstage/backend-tasks@0.5.15-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-adr-backend@0.4.7-next.1 + - @backstage/plugin-app-backend@0.3.58-next.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + - @backstage/plugin-badges-backend@0.3.7-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.1 + - @backstage/plugin-catalog-node@1.6.2-next.1 + - @backstage/plugin-code-coverage-backend@0.2.24-next.1 + - @backstage/plugin-devtools-backend@0.2.7-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.1 + - @backstage/plugin-events-backend@0.2.19-next.1 + - @backstage/plugin-events-node@0.2.19-next.1 + - @backstage/plugin-explore-backend@0.0.20-next.1 + - @backstage/plugin-jenkins-backend@0.3.4-next.1 + - @backstage/plugin-kafka-backend@0.3.8-next.1 + - @backstage/plugin-kubernetes-backend@0.14.2-next.1 + - @backstage/plugin-lighthouse-backend@0.4.2-next.1 + - @backstage/plugin-linguist-backend@0.5.7-next.1 + - @backstage/plugin-nomad-backend@0.1.12-next.1 + - @backstage/plugin-permission-backend@0.5.33-next.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.21-next.1 + - @backstage/plugin-playlist-backend@0.3.14-next.1 + - @backstage/plugin-proxy-backend@0.4.8-next.1 + - @backstage/plugin-rollbar-backend@0.1.55-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.1 + - @backstage/plugin-search-backend@1.5.0-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.19-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.1 + - @backstage/plugin-search-backend-node@1.2.14-next.1 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-signals-backend@0.0.1-next.1 + - @backstage/plugin-signals-node@0.0.1-next.1 + - @backstage/plugin-tech-insights-backend@0.5.24-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.1 + - @backstage/plugin-tech-insights-node@0.4.16-next.1 + - @backstage/plugin-techdocs-backend@1.9.3-next.1 + - @backstage/plugin-todo-backend@0.3.8-next.1 + +## 0.2.92-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.0 + - @backstage/plugin-azure-devops-backend@0.5.2-next.0 + - @backstage/plugin-explore-backend@0.0.20-next.0 + - @backstage/plugin-auth-backend@0.20.4-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-backend@0.14.2-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-search-backend@1.5.0-next.0 + - @backstage/plugin-todo-backend@0.3.8-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-signals-backend@0.0.1-next.0 + - @backstage/plugin-signals-node@0.0.1-next.0 + - @backstage/plugin-scaffolder-backend@1.21.0-next.0 + - @backstage/plugin-app-backend@0.3.58-next.0 + - example-app@0.2.92-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-badges-backend@0.3.7-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.0 + - @backstage/plugin-events-backend@0.2.19-next.0 + - @backstage/plugin-linguist-backend@0.5.7-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-playlist-backend@0.3.14-next.0 + - @backstage/plugin-proxy-backend@0.4.8-next.0 + - @backstage/plugin-rollbar-backend@0.1.55-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.19-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 + - @backstage/plugin-tech-insights-backend@0.5.24-next.0 + - @backstage/plugin-techdocs-backend@1.9.3-next.0 + - @backstage/plugin-adr-backend@0.4.7-next.0 + - @backstage/plugin-azure-sites-backend@0.1.20-next.0 + - @backstage/plugin-code-coverage-backend@0.2.24-next.0 + - @backstage/plugin-devtools-backend@0.2.7-next.0 + - @backstage/plugin-jenkins-backend@0.3.4-next.0 + - @backstage/plugin-kafka-backend@0.3.8-next.0 + - @backstage/plugin-lighthouse-backend@0.4.2-next.0 + - @backstage/plugin-nomad-backend@0.1.12-next.0 + - @backstage/plugin-permission-backend@0.5.33-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.0 + - @backstage/plugin-tech-insights-node@0.4.16-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.91 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.3 + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-backend@1.20.0 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10 + - @backstage/plugin-events-backend@0.2.18 + - @backstage/plugin-search-backend-module-techdocs@0.1.13 + - @backstage/plugin-search-backend-module-catalog@0.1.13 + - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/plugin-azure-devops-backend@0.5.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41 + - @backstage/plugin-entity-feedback-backend@0.2.6 + - @backstage/plugin-code-coverage-backend@0.2.23 + - @backstage/plugin-azure-sites-backend@0.1.19 + - @backstage/plugin-tech-insights-node@0.4.15 + - @backstage/plugin-devtools-backend@0.2.6 + - @backstage/plugin-linguist-backend@0.5.6 + - @backstage/plugin-playlist-backend@0.3.13 + - @backstage/plugin-techdocs-backend@1.9.2 + - @backstage/plugin-explore-backend@0.0.19 + - @backstage/plugin-jenkins-backend@0.3.3 + - @backstage/plugin-badges-backend@0.3.6 + - @backstage/plugin-search-backend@1.4.9 + - @backstage/plugin-kafka-backend@0.3.7 + - @backstage/plugin-nomad-backend@0.1.11 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-todo-backend@0.3.7 + - @backstage/plugin-adr-backend@0.4.6 + - @backstage/plugin-app-backend@0.3.57 + - @backstage/plugin-permission-backend@0.5.32 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-catalog-backend@1.16.1 + - example-app@0.2.91 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-kubernetes-backend@0.14.1 + - @backstage/plugin-lighthouse-backend@0.4.1 + - @backstage/plugin-proxy-backend@0.4.7 + - @backstage/plugin-rollbar-backend@0.1.54 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12 + - @backstage/plugin-search-backend-module-pg@0.5.18 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/plugin-tech-insights-backend@0.5.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 + - @backstage/plugin-events-node@0.2.18 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.91-next.2 + +### Patch Changes + +- Updated dependencies + - example-app@0.2.91-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-adr-backend@0.4.6-next.2 + - @backstage/plugin-app-backend@0.3.57-next.2 + - @backstage/plugin-auth-backend@0.20.3-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-azure-devops-backend@0.5.1-next.2 + - @backstage/plugin-badges-backend@0.3.6-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-code-coverage-backend@0.2.23-next.2 + - @backstage/plugin-devtools-backend@0.2.6-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.2 + - @backstage/plugin-events-backend@0.2.18-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/plugin-jenkins-backend@0.3.3-next.2 + - @backstage/plugin-kafka-backend@0.3.7-next.2 + - @backstage/plugin-kubernetes-backend@0.14.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.1-next.2 + - @backstage/plugin-linguist-backend@0.5.6-next.2 + - @backstage/plugin-nomad-backend@0.1.11-next.2 + - @backstage/plugin-permission-backend@0.5.32-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-playlist-backend@0.3.13-next.2 + - @backstage/plugin-proxy-backend@0.4.7-next.2 + - @backstage/plugin-scaffolder-backend@1.19.3-next.2 + - @backstage/plugin-search-backend@1.4.9-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/plugin-techdocs-backend@1.9.2-next.2 + - @backstage/plugin-todo-backend@0.3.7-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-azure-sites-backend@0.1.19-next.2 + - @backstage/plugin-explore-backend@0.0.19-next.2 + - @backstage/plugin-rollbar-backend@0.1.54-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.2 + - @backstage/plugin-tech-insights-backend@0.5.23-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.2 + - @backstage/plugin-tech-insights-node@0.4.15-next.2 + +## 0.2.91-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.1 + - example-app@0.2.91-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-app-backend@0.3.57-next.1 + - @backstage/plugin-devtools-backend@0.2.6-next.1 + - @backstage/plugin-proxy-backend@0.4.7-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-kubernetes-backend@0.14.1-next.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-adr-backend@0.4.6-next.1 + - @backstage/plugin-auth-backend@0.20.3-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-azure-devops-backend@0.5.1-next.1 + - @backstage/plugin-azure-sites-backend@0.1.19-next.1 + - @backstage/plugin-badges-backend@0.3.6-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.23-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.1 + - @backstage/plugin-events-backend@0.2.18-next.1 + - @backstage/plugin-explore-backend@0.0.19-next.1 + - @backstage/plugin-jenkins-backend@0.3.3-next.1 + - @backstage/plugin-kafka-backend@0.3.7-next.1 + - @backstage/plugin-lighthouse-backend@0.4.1-next.1 + - @backstage/plugin-linguist-backend@0.5.6-next.1 + - @backstage/plugin-nomad-backend@0.1.11-next.1 + - @backstage/plugin-permission-backend@0.5.32-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-playlist-backend@0.3.13-next.1 + - @backstage/plugin-rollbar-backend@0.1.54-next.1 + - @backstage/plugin-scaffolder-backend@1.19.3-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.1 + - @backstage/plugin-search-backend@1.4.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/plugin-tech-insights-backend@0.5.23-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.1 + - @backstage/plugin-tech-insights-node@0.4.15-next.1 + - @backstage/plugin-techdocs-backend@1.9.2-next.1 + - @backstage/plugin-todo-backend@0.3.7-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.3-next.0 + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-scaffolder-backend@1.19.3-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + - @backstage/plugin-azure-devops-backend@0.5.1-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.0 + - @backstage/plugin-code-coverage-backend@0.2.23-next.0 + - @backstage/plugin-azure-sites-backend@0.1.19-next.0 + - @backstage/plugin-tech-insights-node@0.4.15-next.0 + - @backstage/plugin-devtools-backend@0.2.6-next.0 + - @backstage/plugin-linguist-backend@0.5.6-next.0 + - @backstage/plugin-playlist-backend@0.3.13-next.0 + - @backstage/plugin-techdocs-backend@1.9.2-next.0 + - @backstage/plugin-explore-backend@0.0.19-next.0 + - @backstage/plugin-jenkins-backend@0.3.3-next.0 + - @backstage/plugin-badges-backend@0.3.6-next.0 + - @backstage/plugin-search-backend@1.4.9-next.0 + - @backstage/plugin-kafka-backend@0.3.7-next.0 + - @backstage/plugin-nomad-backend@0.1.11-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/plugin-todo-backend@0.3.7-next.0 + - @backstage/plugin-adr-backend@0.4.6-next.0 + - @backstage/plugin-app-backend@0.3.57-next.0 + - example-app@0.2.91-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 + - @backstage/plugin-events-backend@0.2.18-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-kubernetes-backend@0.14.1-next.0 + - @backstage/plugin-lighthouse-backend@0.4.1-next.0 + - @backstage/plugin-permission-backend@0.5.32-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-proxy-backend@0.4.7-next.0 + - @backstage/plugin-rollbar-backend@0.1.54-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + - @backstage/plugin-tech-insights-backend@0.5.23-next.0 + +## 0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.1 + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-techdocs-backend@1.9.1 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-azure-devops-backend@0.5.0 + - @backstage/plugin-scaffolder-backend@1.19.2 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-lighthouse-backend@0.4.0 + - @backstage/plugin-kubernetes-backend@0.14.0 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-sites-backend@0.1.18 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-backend@0.5.31 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-backend@0.3.12 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend@1.4.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 + - @backstage/plugin-search-backend-module-techdocs@0.1.12 + - @backstage/plugin-search-backend-module-catalog@0.1.12 + - @backstage/plugin-search-backend-module-explore@0.1.12 + - @backstage/plugin-search-backend-module-pg@0.5.17 + - @backstage/plugin-events-backend@0.2.17 + - example-app@0.2.90 + - @backstage/plugin-adr-backend@0.4.5 + - @backstage/plugin-app-backend@0.3.56 + - @backstage/plugin-badges-backend@0.3.5 + - @backstage/plugin-code-coverage-backend@0.2.22 + - @backstage/plugin-devtools-backend@0.2.5 + - @backstage/plugin-entity-feedback-backend@0.2.5 + - @backstage/plugin-explore-backend@0.0.18 + - @backstage/plugin-jenkins-backend@0.3.2 + - @backstage/plugin-kafka-backend@0.3.6 + - @backstage/plugin-linguist-backend@0.5.5 + - @backstage/plugin-nomad-backend@0.1.10 + - @backstage/plugin-proxy-backend@0.4.6 + - @backstage/plugin-rollbar-backend@0.1.53 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/plugin-tech-insights-backend@0.5.22 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40 + - @backstage/plugin-tech-insights-node@0.4.14 + - @backstage/plugin-todo-backend@0.3.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.90-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.5.0-next.3 + - @backstage/plugin-scaffolder-backend@1.19.2-next.3 + - @backstage/backend-common@0.20.0-next.3 + - example-app@0.2.90-next.4 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.3 + - @backstage/plugin-app-backend@0.3.56-next.3 + - @backstage/plugin-auth-backend@0.20.1-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-azure-sites-backend@0.1.18-next.3 + - @backstage/plugin-badges-backend@0.3.5-next.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-code-coverage-backend@0.2.22-next.3 + - @backstage/plugin-devtools-backend@0.2.5-next.3 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.3 + - @backstage/plugin-events-backend@0.2.17-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-explore-backend@0.0.18-next.3 + - @backstage/plugin-jenkins-backend@0.3.2-next.3 + - @backstage/plugin-kafka-backend@0.3.6-next.3 + - @backstage/plugin-kubernetes-backend@0.14.0-next.3 + - @backstage/plugin-lighthouse-backend@0.4.0-next.3 + - @backstage/plugin-linguist-backend@0.5.5-next.3 + - @backstage/plugin-nomad-backend@0.1.10-next.3 + - @backstage/plugin-permission-backend@0.5.31-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-playlist-backend@0.3.12-next.3 + - @backstage/plugin-proxy-backend@0.4.6-next.3 + - @backstage/plugin-rollbar-backend@0.1.53-next.3 + - @backstage/plugin-search-backend@1.4.8-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.3 + - @backstage/plugin-tech-insights-node@0.4.14-next.3 + - @backstage/plugin-techdocs-backend@1.9.1-next.3 + - @backstage/plugin-todo-backend@0.3.6-next.3 + +## 0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/plugin-auth-backend@0.20.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.2 + - @backstage/plugin-events-backend@0.2.17-next.2 + - example-app@0.2.90-next.3 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.2 + - @backstage/plugin-app-backend@0.3.56-next.2 + - @backstage/plugin-azure-devops-backend@0.5.0-next.2 + - @backstage/plugin-azure-sites-backend@0.1.18-next.2 + - @backstage/plugin-badges-backend@0.3.5-next.2 + - @backstage/plugin-code-coverage-backend@0.2.22-next.2 + - @backstage/plugin-devtools-backend@0.2.5-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-explore-backend@0.0.18-next.2 + - @backstage/plugin-jenkins-backend@0.3.2-next.2 + - @backstage/plugin-kafka-backend@0.3.6-next.2 + - @backstage/plugin-kubernetes-backend@0.14.0-next.2 + - @backstage/plugin-linguist-backend@0.5.5-next.2 + - @backstage/plugin-nomad-backend@0.1.10-next.2 + - @backstage/plugin-permission-backend@0.5.31-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-playlist-backend@0.3.12-next.2 + - @backstage/plugin-proxy-backend@0.4.6-next.2 + - @backstage/plugin-rollbar-backend@0.1.53-next.2 + - @backstage/plugin-scaffolder-backend@1.19.2-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.2 + - @backstage/plugin-search-backend@1.4.8-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.2 + - @backstage/plugin-tech-insights-node@0.4.14-next.2 + - @backstage/plugin-techdocs-backend@1.9.1-next.2 + - @backstage/plugin-todo-backend@0.3.6-next.2 + +## 0.2.90-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.1-next.1 + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-azure-devops-backend@0.5.0-next.1 + - @backstage/plugin-kubernetes-backend@0.14.0-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-azure-sites-backend@0.1.18-next.1 + - @backstage/backend-common@0.20.0-next.1 + - example-app@0.2.90-next.2 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-adr-backend@0.4.5-next.1 + - @backstage/plugin-app-backend@0.3.56-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-badges-backend@0.3.5-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.22-next.1 + - @backstage/plugin-devtools-backend@0.2.5-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.1 + - @backstage/plugin-events-backend@0.2.17-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-explore-backend@0.0.18-next.1 + - @backstage/plugin-jenkins-backend@0.3.2-next.1 + - @backstage/plugin-kafka-backend@0.3.6-next.1 + - @backstage/plugin-lighthouse-backend@0.3.5-next.1 + - @backstage/plugin-linguist-backend@0.5.5-next.1 + - @backstage/plugin-nomad-backend@0.1.10-next.1 + - @backstage/plugin-permission-backend@0.5.31-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-playlist-backend@0.3.12-next.1 + - @backstage/plugin-proxy-backend@0.4.6-next.1 + - @backstage/plugin-rollbar-backend@0.1.53-next.1 + - @backstage/plugin-scaffolder-backend@1.19.2-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.1 + - @backstage/plugin-search-backend@1.4.8-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.1 + - @backstage/plugin-tech-insights-node@0.4.14-next.1 + - @backstage/plugin-techdocs-backend@1.9.1-next.1 + - @backstage/plugin-todo-backend@0.3.6-next.1 + +## 0.2.90-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/plugin-auth-backend@0.20.1-next.0 + - @backstage/backend-tasks@0.5.13-next.0 + - @backstage/plugin-scaffolder-backend@1.19.2-next.0 + - @backstage/plugin-kubernetes-backend@0.14.0-next.0 + - @backstage/plugin-azure-sites-backend@0.1.18-next.0 + - @backstage/integration@1.8.0-next.0 + - example-app@0.2.90-next.0 + - @backstage/plugin-adr-backend@0.4.5-next.0 + - @backstage/plugin-app-backend@0.3.56-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/plugin-azure-devops-backend@0.4.5-next.0 + - @backstage/plugin-badges-backend@0.3.5-next.0 + - @backstage/plugin-catalog-backend@1.15.1-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.0 + - @backstage/plugin-catalog-node@1.5.1-next.0 + - @backstage/plugin-code-coverage-backend@0.2.22-next.0 + - @backstage/plugin-devtools-backend@0.2.5-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.0 + - @backstage/plugin-events-backend@0.2.17-next.0 + - @backstage/plugin-explore-backend@0.0.18-next.0 + - @backstage/plugin-jenkins-backend@0.3.2-next.0 + - @backstage/plugin-kafka-backend@0.3.6-next.0 + - @backstage/plugin-lighthouse-backend@0.3.5-next.0 + - @backstage/plugin-linguist-backend@0.5.5-next.0 + - @backstage/plugin-nomad-backend@0.1.10-next.0 + - @backstage/plugin-permission-backend@0.5.31-next.0 + - @backstage/plugin-permission-node@0.7.19-next.0 + - @backstage/plugin-playlist-backend@0.3.12-next.0 + - @backstage/plugin-proxy-backend@0.4.6-next.0 + - @backstage/plugin-rollbar-backend@0.1.53-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.0 + - @backstage/plugin-search-backend@1.4.8-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.0 + - @backstage/plugin-search-backend-node@1.2.12-next.0 + - @backstage/plugin-tech-insights-backend@0.5.22-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.0 + - @backstage/plugin-tech-insights-node@0.4.14-next.0 + - @backstage/plugin-techdocs-backend@1.9.1-next.0 + - @backstage/plugin-todo-backend@0.3.6-next.0 + - @backstage/catalog-client@1.4.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.0 + - @backstage/plugin-events-node@0.2.17-next.0 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-common@1.2.8 + +## 0.2.89 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.0 + - @backstage/plugin-catalog-node@1.5.0 + - @backstage/plugin-search-backend-module-pg@0.5.16 + - @backstage/plugin-kubernetes-backend@0.13.1 + - @backstage/plugin-search-backend-node@1.2.11 + - @backstage/integration@1.7.2 + - @backstage/plugin-auth-backend@0.20.0 + - @backstage/backend-common@0.19.9 + - @backstage/plugin-techdocs-backend@1.9.0 + - @backstage/plugin-code-coverage-backend@0.2.21 + - @backstage/plugin-scaffolder-backend@1.19.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.10 + - @backstage/plugin-search-backend@1.4.7 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4 + - @backstage/plugin-entity-feedback-backend@0.2.4 + - @backstage/plugin-tech-insights-backend@0.5.21 + - @backstage/plugin-linguist-backend@0.5.4 + - @backstage/plugin-playlist-backend@0.3.11 + - @backstage/backend-tasks@0.5.12 + - @backstage/plugin-badges-backend@0.3.4 + - @backstage/plugin-app-backend@0.3.55 + - @backstage/plugin-search-backend-module-techdocs@0.1.11 + - @backstage/catalog-client@1.4.6 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-jenkins-backend@0.3.1 + - @backstage/plugin-adr-backend@0.4.4 + - @backstage/plugin-kafka-backend@0.3.5 + - @backstage/plugin-proxy-backend@0.4.5 + - example-app@0.2.89 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4 + - @backstage/plugin-lighthouse-backend@0.3.4 + - @backstage/plugin-search-backend-module-catalog@0.1.11 + - @backstage/plugin-todo-backend@0.3.5 + - @backstage/plugin-devtools-backend@0.2.4 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.1 + - @backstage/plugin-azure-devops-backend@0.4.4 + - @backstage/plugin-azure-sites-backend@0.1.17 + - @backstage/plugin-events-backend@0.2.16 + - @backstage/plugin-events-node@0.2.16 + - @backstage/plugin-explore-backend@0.0.17 + - @backstage/plugin-graphql-backend@0.2.1 + - @backstage/plugin-nomad-backend@0.1.9 + - @backstage/plugin-permission-backend@0.5.30 + - @backstage/plugin-permission-node@0.7.18 + - @backstage/plugin-rollbar-backend@0.1.52 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.24 + - @backstage/plugin-search-backend-module-explore@0.1.11 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39 + - @backstage/plugin-tech-insights-node@0.4.13 + +## 0.2.89-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.13.1-next.2 + - @backstage/plugin-scaffolder-backend@1.19.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.10-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.16-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.4-next.2 + - @backstage/plugin-code-coverage-backend@0.2.21-next.2 + - @backstage/plugin-tech-insights-backend@0.5.21-next.2 + - @backstage/plugin-linguist-backend@0.5.4-next.2 + - @backstage/plugin-playlist-backend@0.3.11-next.2 + - @backstage/plugin-techdocs-backend@1.9.0-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/plugin-catalog-backend@1.15.0-next.2 + - @backstage/backend-tasks@0.5.12-next.2 + - @backstage/plugin-badges-backend@0.3.4-next.2 + - @backstage/plugin-auth-backend@0.20.0-next.2 + - @backstage/plugin-app-backend@0.3.55-next.2 + - example-app@0.2.89-next.2 + - @backstage/plugin-adr-backend@0.4.4-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + - @backstage/plugin-azure-devops-backend@0.4.4-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.2 + - @backstage/plugin-catalog-node@1.5.0-next.2 + - @backstage/plugin-devtools-backend@0.2.4-next.2 + - @backstage/plugin-events-backend@0.2.16-next.2 + - @backstage/plugin-events-node@0.2.16-next.2 + - @backstage/plugin-jenkins-backend@0.3.1-next.2 + - @backstage/plugin-kafka-backend@0.3.5-next.2 + - @backstage/plugin-lighthouse-backend@0.3.4-next.2 + - @backstage/plugin-nomad-backend@0.1.9-next.2 + - @backstage/plugin-permission-backend@0.5.30-next.2 + - @backstage/plugin-permission-node@0.7.18-next.2 + - @backstage/plugin-proxy-backend@0.4.5-next.2 + - @backstage/plugin-search-backend@1.4.7-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.11-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.11-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.2 + - @backstage/plugin-search-backend-node@1.2.11-next.2 + - @backstage/plugin-todo-backend@0.3.5-next.2 + - @backstage/plugin-rollbar-backend@0.1.52-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.24-next.2 + - @backstage/plugin-azure-sites-backend@0.1.17-next.2 + - @backstage/plugin-explore-backend@0.0.17-next.2 + - @backstage/plugin-graphql-backend@0.2.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39-next.2 + - @backstage/plugin-tech-insights-node@0.4.13-next.2 + +## 0.2.89-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.0-next.1 + - @backstage/plugin-catalog-node@1.5.0-next.1 + - @backstage/integration@1.7.2-next.0 + - @backstage/plugin-auth-backend@0.20.0-next.1 + - @backstage/plugin-techdocs-backend@1.9.0-next.1 + - @backstage/plugin-scaffolder-backend@1.19.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.1 + - @backstage/plugin-jenkins-backend@0.3.1-next.1 + - @backstage/plugin-kubernetes-backend@0.13.1-next.1 + - @backstage/plugin-lighthouse-backend@0.3.4-next.1 + - @backstage/plugin-linguist-backend@0.5.4-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.11-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.1 + - @backstage/plugin-todo-backend@0.3.5-next.1 + - example-app@0.2.89-next.1 + - @backstage/backend-common@0.19.9-next.1 + - @backstage/plugin-adr-backend@0.4.4-next.1 + - @backstage/plugin-code-coverage-backend@0.2.21-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.24-next.1 + - @backstage/backend-tasks@0.5.12-next.1 + - @backstage/plugin-app-backend@0.3.55-next.1 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/plugin-badges-backend@0.3.4-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.4-next.1 + - @backstage/plugin-events-backend@0.2.16-next.1 + - @backstage/plugin-permission-node@0.7.18-next.1 + - @backstage/plugin-playlist-backend@0.3.11-next.1 + - @backstage/plugin-proxy-backend@0.4.5-next.1 + - @backstage/plugin-rollbar-backend@0.1.52-next.1 + - @backstage/plugin-search-backend@1.4.7-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.11-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.16-next.1 + - @backstage/plugin-tech-insights-backend@0.5.21-next.1 + - @backstage/plugin-azure-devops-backend@0.4.4-next.1 + - @backstage/plugin-azure-sites-backend@0.1.17-next.1 + - @backstage/plugin-devtools-backend@0.2.4-next.1 + - @backstage/plugin-explore-backend@0.0.17-next.1 + - @backstage/plugin-graphql-backend@0.2.1-next.1 + - @backstage/plugin-kafka-backend@0.3.5-next.1 + - @backstage/plugin-nomad-backend@0.1.9-next.1 + - @backstage/plugin-permission-backend@0.5.30-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.10-next.1 + - @backstage/plugin-search-backend-node@1.2.11-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39-next.1 + - @backstage/plugin-tech-insights-node@0.4.13-next.1 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.1 + - @backstage/plugin-events-node@0.2.16-next.1 + - @backstage/plugin-permission-common@0.7.9 + - @backstage/plugin-search-common@1.2.7 + +## 0.2.89-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.11-next.0 + - @backstage/plugin-techdocs-backend@1.8.1-next.0 + - @backstage/plugin-code-coverage-backend@0.2.21-next.0 + - @backstage/plugin-scaffolder-backend@1.19.0-next.0 + - @backstage/plugin-catalog-backend@1.15.0-next.0 + - @backstage/plugin-search-backend@1.4.7-next.0 + - @backstage/plugin-tech-insights-backend@0.5.21-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.0 + - @backstage/plugin-kafka-backend@0.3.5-next.0 + - @backstage/plugin-proxy-backend@0.4.5-next.0 + - @backstage/plugin-auth-backend@0.20.0-next.0 + - @backstage/backend-common@0.19.9-next.0 + - @backstage/integration@1.7.1 + - @backstage/plugin-app-backend@0.3.55-next.0 + - @backstage/plugin-devtools-backend@0.2.4-next.0 + - example-app@0.2.89-next.0 + - @backstage/backend-tasks@0.5.12-next.0 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-adr-backend@0.4.4-next.0 + - @backstage/plugin-auth-node@0.4.1-next.0 + - @backstage/plugin-azure-devops-backend@0.4.4-next.0 + - @backstage/plugin-azure-sites-backend@0.1.17-next.0 + - @backstage/plugin-badges-backend@0.3.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.0 + - @backstage/plugin-catalog-node@1.4.8-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.4-next.0 + - @backstage/plugin-events-backend@0.2.16-next.0 + - @backstage/plugin-events-node@0.2.16-next.0 + - @backstage/plugin-explore-backend@0.0.17-next.0 + - @backstage/plugin-graphql-backend@0.2.1-next.0 + - @backstage/plugin-jenkins-backend@0.3.1-next.0 + - @backstage/plugin-kubernetes-backend@0.13.1-next.0 + - @backstage/plugin-lighthouse-backend@0.3.4-next.0 + - @backstage/plugin-linguist-backend@0.5.4-next.0 + - @backstage/plugin-nomad-backend@0.1.9-next.0 + - @backstage/plugin-permission-backend@0.5.30-next.0 + - @backstage/plugin-permission-common@0.7.9 + - @backstage/plugin-permission-node@0.7.18-next.0 + - @backstage/plugin-playlist-backend@0.3.11-next.0 + - @backstage/plugin-rollbar-backend@0.1.52-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.24-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.11-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.10-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.11-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.16-next.0 + - @backstage/plugin-search-common@1.2.7 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39-next.0 + - @backstage/plugin-tech-insights-node@0.4.13-next.0 + - @backstage/plugin-todo-backend@0.3.5-next.0 + +## 0.2.88 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-nomad-backend@0.1.8 + - @backstage/backend-tasks@0.5.11 + - @backstage/backend-common@0.19.8 + - @backstage/plugin-scaffolder-backend@1.18.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.9 + - @backstage/integration@1.7.1 + - @backstage/plugin-playlist-backend@0.3.10 + - @backstage/plugin-techdocs-backend@1.8.0 + - @backstage/plugin-auth-backend@0.19.3 + - @backstage/plugin-rollbar-backend@0.1.51 + - @backstage/plugin-catalog-backend@1.14.0 + - @backstage/plugin-catalog-node@1.4.7 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/plugin-graphql-backend@0.2.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-badges-backend@0.3.3 + - @backstage/plugin-tech-insights-backend@0.5.20 + - @backstage/plugin-kubernetes-backend@0.13.0 + - @backstage/plugin-jenkins-backend@0.3.0 + - @backstage/plugin-code-coverage-backend@0.2.20 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.23 + - @backstage/plugin-search-backend@1.4.6 + - example-app@0.2.88 + - @backstage/plugin-lighthouse-backend@0.3.3 + - @backstage/plugin-linguist-backend@0.5.3 + - @backstage/plugin-search-backend-module-catalog@0.1.10 + - @backstage/plugin-search-backend-module-explore@0.1.10 + - @backstage/plugin-search-backend-module-techdocs@0.1.10 + - @backstage/plugin-search-backend-node@1.2.10 + - @backstage/plugin-tech-insights-node@0.4.12 + - @backstage/plugin-adr-backend@0.4.3 + - @backstage/plugin-app-backend@0.3.54 + - @backstage/plugin-azure-devops-backend@0.4.3 + - @backstage/plugin-azure-sites-backend@0.1.16 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.3 + - @backstage/plugin-devtools-backend@0.2.3 + - @backstage/plugin-entity-feedback-backend@0.2.3 + - @backstage/plugin-events-backend@0.2.15 + - @backstage/plugin-explore-backend@0.0.16 + - @backstage/plugin-kafka-backend@0.3.3 + - @backstage/plugin-permission-backend@0.5.29 + - @backstage/plugin-permission-node@0.7.17 + - @backstage/plugin-proxy-backend@0.4.3 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.7 + - @backstage/plugin-search-backend-module-pg@0.5.15 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.38 + - @backstage/plugin-todo-backend@0.3.4 + - @backstage/catalog-client@1.4.5 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.3 + - @backstage/plugin-events-node@0.2.15 + - @backstage/plugin-permission-common@0.7.9 + - @backstage/plugin-search-common@1.2.7 + +## 0.2.88-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-nomad-backend@0.1.8-next.2 + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-scaffolder-backend@1.18.0-next.2 + - @backstage/plugin-techdocs-backend@1.8.0-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/plugin-catalog-backend@1.14.0-next.2 + - @backstage/catalog-model@1.4.3-next.0 + - @backstage/integration@1.7.1-next.1 + - @backstage/plugin-kubernetes-backend@0.12.3-next.2 + - @backstage/plugin-jenkins-backend@0.2.9-next.2 + - @backstage/plugin-auth-backend@0.19.3-next.2 + - @backstage/backend-tasks@0.5.11-next.2 + - @backstage/plugin-adr-backend@0.4.3-next.2 + - @backstage/plugin-app-backend@0.3.54-next.2 + - @backstage/plugin-azure-devops-backend@0.4.3-next.2 + - @backstage/plugin-azure-sites-backend@0.1.16-next.2 + - @backstage/plugin-badges-backend@0.3.3-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.3-next.2 + - @backstage/plugin-catalog-node@1.4.7-next.2 + - @backstage/plugin-code-coverage-backend@0.2.20-next.2 + - @backstage/plugin-devtools-backend@0.2.3-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.3-next.2 + - @backstage/plugin-events-backend@0.2.15-next.2 + - @backstage/plugin-explore-backend@0.0.16-next.2 + - @backstage/plugin-graphql-backend@0.1.44-next.2 + - @backstage/plugin-kafka-backend@0.3.3-next.2 + - @backstage/plugin-lighthouse-backend@0.3.3-next.2 + - @backstage/plugin-linguist-backend@0.5.3-next.2 + - @backstage/plugin-permission-backend@0.5.29-next.2 + - @backstage/plugin-permission-node@0.7.17-next.2 + - @backstage/plugin-playlist-backend@0.3.10-next.2 + - @backstage/plugin-proxy-backend@0.4.3-next.2 + - @backstage/plugin-rollbar-backend@0.1.51-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.7-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.23-next.2 + - @backstage/plugin-search-backend@1.4.6-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.10-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.9-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.10-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.15-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.10-next.2 + - @backstage/plugin-search-backend-node@1.2.10-next.2 + - @backstage/plugin-tech-insights-backend@0.5.20-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.38-next.2 + - @backstage/plugin-tech-insights-node@0.4.12-next.2 + - @backstage/plugin-todo-backend@0.3.4-next.2 + - example-app@0.2.88-next.2 + - @backstage/catalog-client@1.4.5-next.0 + - @backstage/config@1.1.1-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.3-next.2 + - @backstage/plugin-events-node@0.2.15-next.2 + - @backstage/plugin-permission-common@0.7.9-next.0 + - @backstage/plugin-search-common@1.2.7-next.0 + +## 0.2.88-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.10-next.1 + - @backstage/plugin-catalog-backend@1.14.0-next.1 + - @backstage/plugin-catalog-node@1.4.6-next.1 + - @backstage/backend-common@0.19.7-next.1 + - @backstage/plugin-scaffolder-backend@1.18.0-next.1 + - @backstage/plugin-badges-backend@0.3.2-next.1 + - @backstage/plugin-lighthouse-backend@0.3.2-next.1 + - @backstage/plugin-linguist-backend@0.5.2-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.9-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.9-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.9-next.1 + - @backstage/plugin-search-backend-node@1.2.9-next.1 + - @backstage/plugin-tech-insights-backend@0.5.19-next.1 + - @backstage/plugin-tech-insights-node@0.4.11-next.1 + - example-app@0.2.88-next.1 + - @backstage/plugin-auth-backend@0.19.2-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.2-next.1 + - @backstage/plugin-kubernetes-backend@0.12.2-next.1 + - @backstage/plugin-todo-backend@0.3.3-next.1 + - @backstage/plugin-adr-backend@0.4.2-next.1 + - @backstage/plugin-app-backend@0.3.53-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + - @backstage/plugin-azure-devops-backend@0.4.2-next.1 + - @backstage/plugin-azure-sites-backend@0.1.15-next.1 + - @backstage/plugin-code-coverage-backend@0.2.19-next.1 + - @backstage/plugin-devtools-backend@0.2.2-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.2-next.1 + - @backstage/plugin-events-backend@0.2.14-next.1 + - @backstage/plugin-explore-backend@0.0.15-next.1 + - @backstage/plugin-graphql-backend@0.1.43-next.1 + - @backstage/plugin-jenkins-backend@0.2.8-next.1 + - @backstage/plugin-kafka-backend@0.3.2-next.1 + - @backstage/plugin-nomad-backend@0.1.7-next.1 + - @backstage/plugin-permission-backend@0.5.28-next.1 + - @backstage/plugin-permission-node@0.7.16-next.1 + - @backstage/plugin-playlist-backend@0.3.9-next.1 + - @backstage/plugin-proxy-backend@0.4.2-next.1 + - @backstage/plugin-rollbar-backend@0.1.50-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.6-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.22-next.1 + - @backstage/plugin-search-backend@1.4.5-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.8-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.14-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.37-next.1 + - @backstage/plugin-techdocs-backend@1.7.2-next.1 + - @backstage/config@1.1.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/integration@1.7.1-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.2-next.1 + - @backstage/plugin-events-node@0.2.14-next.1 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-search-common@1.2.6 + +## 0.2.88-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-elasticsearch@1.3.8-next.0 + - @backstage/integration@1.7.1-next.0 + - @backstage/plugin-playlist-backend@0.3.9-next.0 + - @backstage/plugin-rollbar-backend@0.1.50-next.0 + - @backstage/plugin-catalog-backend@1.14.0-next.0 + - @backstage/plugin-tech-insights-backend@0.5.19-next.0 + - @backstage/plugin-code-coverage-backend@0.2.19-next.0 + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.22-next.0 + - @backstage/backend-common@0.19.7-next.0 + - example-app@0.2.88-next.0 + - @backstage/plugin-adr-backend@0.4.2-next.0 + - @backstage/plugin-scaffolder-backend@1.17.3-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.6-next.0 + - @backstage/plugin-techdocs-backend@1.7.2-next.0 + - @backstage/plugin-todo-backend@0.3.3-next.0 + - @backstage/config@1.1.0 + - @backstage/backend-tasks@0.5.10-next.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/plugin-app-backend@0.3.53-next.0 + - @backstage/plugin-auth-backend@0.19.2-next.0 + - @backstage/plugin-azure-devops-backend@0.4.2-next.0 + - @backstage/plugin-azure-sites-backend@0.1.15-next.0 + - @backstage/plugin-badges-backend@0.3.2-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.2-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.2-next.0 + - @backstage/plugin-catalog-node@1.4.6-next.0 + - @backstage/plugin-devtools-backend@0.2.2-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.2-next.0 + - @backstage/plugin-events-backend@0.2.14-next.0 + - @backstage/plugin-events-node@0.2.14-next.0 + - @backstage/plugin-explore-backend@0.0.15-next.0 + - @backstage/plugin-graphql-backend@0.1.43-next.0 + - @backstage/plugin-jenkins-backend@0.2.8-next.0 + - @backstage/plugin-kafka-backend@0.3.2-next.0 + - @backstage/plugin-kubernetes-backend@0.12.2-next.0 + - @backstage/plugin-lighthouse-backend@0.3.2-next.0 + - @backstage/plugin-linguist-backend@0.5.2-next.0 + - @backstage/plugin-nomad-backend@0.1.7-next.0 + - @backstage/plugin-permission-backend@0.5.28-next.0 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-permission-node@0.7.16-next.0 + - @backstage/plugin-proxy-backend@0.4.2-next.0 + - @backstage/plugin-search-backend@1.4.5-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.9-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.9-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.14-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.9-next.0 + - @backstage/plugin-search-backend-node@1.2.9-next.0 + - @backstage/plugin-search-common@1.2.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.37-next.0 + - @backstage/plugin-tech-insights-node@0.4.11-next.0 + +## 0.2.87 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-pg@0.5.12 + - @backstage/plugin-catalog-backend@1.13.0 + - @backstage/plugin-kubernetes-backend@0.12.0 + - @backstage/plugin-techdocs-backend@1.7.0 + - @backstage/plugin-auth-backend@0.19.0 + - @backstage/plugin-proxy-backend@0.4.0 + - @backstage/plugin-adr-backend@0.4.0 + - @backstage/plugin-azure-devops-backend@0.4.0 + - @backstage/plugin-badges-backend@0.3.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.0 + - @backstage/plugin-devtools-backend@0.2.0 + - @backstage/plugin-entity-feedback-backend@0.2.0 + - @backstage/plugin-kafka-backend@0.3.0 + - @backstage/plugin-lighthouse-backend@0.3.0 + - @backstage/plugin-linguist-backend@0.5.0 + - @backstage/plugin-todo-backend@0.3.0 + - @backstage/plugin-app-backend@0.3.51 + - @backstage/plugin-events-backend@0.2.12 + - @backstage/plugin-permission-backend@0.5.26 + - @backstage/plugin-scaffolder-backend@1.17.0 + - @backstage/plugin-search-backend@1.4.3 + - @backstage/plugin-search-backend-module-catalog@0.1.7 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.6 + - @backstage/plugin-search-backend-module-explore@0.1.7 + - @backstage/plugin-search-backend-module-techdocs@0.1.7 + - @backstage/plugin-code-coverage-backend@0.2.17 + - @backstage/backend-tasks@0.5.8 + - @backstage/backend-common@0.19.5 + - @backstage/plugin-auth-node@0.3.0 + - @backstage/config@1.1.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/integration@1.7.0 + - @backstage/plugin-permission-common@0.7.8 + - @backstage/plugin-search-common@1.2.6 + - @backstage/plugin-permission-node@0.7.14 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.0 + - @backstage/plugin-tech-insights-backend@0.5.17 + - example-app@0.2.87 + - @backstage/plugin-catalog-node@1.4.4 + - @backstage/plugin-playlist-backend@0.3.7 + - @backstage/plugin-rollbar-backend@0.1.48 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4 + - @backstage/plugin-azure-sites-backend@0.1.13 + - @backstage/plugin-events-node@0.2.12 + - @backstage/plugin-explore-backend@0.0.13 + - @backstage/plugin-graphql-backend@0.1.41 + - @backstage/plugin-jenkins-backend@0.2.6 + - @backstage/plugin-nomad-backend@0.1.5 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.20 + - @backstage/plugin-search-backend-node@1.2.7 + - @backstage/plugin-tech-insights-node@0.4.9 + +## 0.2.87-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.7.0-next.3 + - @backstage/plugin-proxy-backend@0.4.0-next.3 + - @backstage/plugin-adr-backend@0.4.0-next.3 + - @backstage/plugin-auth-backend@0.19.0-next.3 + - @backstage/plugin-azure-devops-backend@0.4.0-next.3 + - @backstage/plugin-badges-backend@0.3.0-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.0-next.3 + - @backstage/plugin-devtools-backend@0.2.0-next.3 + - @backstage/plugin-entity-feedback-backend@0.2.0-next.3 + - @backstage/plugin-kafka-backend@0.3.0-next.3 + - @backstage/plugin-lighthouse-backend@0.3.0-next.3 + - @backstage/plugin-linguist-backend@0.5.0-next.3 + - @backstage/plugin-todo-backend@0.3.0-next.3 + - @backstage/plugin-app-backend@0.3.51-next.3 + - @backstage/plugin-catalog-backend@1.13.0-next.3 + - @backstage/plugin-events-backend@0.2.12-next.3 + - @backstage/plugin-kubernetes-backend@0.11.6-next.3 + - @backstage/plugin-permission-backend@0.5.26-next.3 + - @backstage/plugin-scaffolder-backend@1.17.0-next.3 + - @backstage/plugin-search-backend@1.4.3-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.7-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.6-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.7-next.3 + - @backstage/plugin-search-backend-module-pg@0.5.12-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.3 + - @backstage/catalog-client@1.4.4-next.2 + - @backstage/catalog-model@1.4.2-next.2 + - @backstage/config@1.1.0-next.2 + - @backstage/integration@1.7.0-next.3 + - @backstage/plugin-permission-common@0.7.8-next.2 + - @backstage/plugin-search-common@1.2.6-next.2 + - @backstage/plugin-permission-node@0.7.14-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.0-next.0 + - example-app@0.2.87-next.3 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/plugin-explore-backend@0.0.13-next.3 + - @backstage/backend-tasks@0.5.8-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + - @backstage/plugin-azure-sites-backend@0.1.13-next.3 + - @backstage/plugin-catalog-node@1.4.4-next.3 + - @backstage/plugin-code-coverage-backend@0.2.17-next.3 + - @backstage/plugin-events-node@0.2.12-next.3 + - @backstage/plugin-graphql-backend@0.1.41-next.3 + - @backstage/plugin-jenkins-backend@0.2.6-next.3 + - @backstage/plugin-nomad-backend@0.1.5-next.3 + - @backstage/plugin-playlist-backend@0.3.7-next.3 + - @backstage/plugin-rollbar-backend@0.1.48-next.3 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.20-next.3 + - @backstage/plugin-search-backend-node@1.2.7-next.3 + - @backstage/plugin-tech-insights-backend@0.5.17-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35-next.3 + - @backstage/plugin-tech-insights-node@0.4.9-next.3 + +## 0.2.87-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.16.6-next.2 + - @backstage/plugin-code-coverage-backend@0.2.17-next.2 + - @backstage/plugin-permission-backend@0.5.26-next.2 + - @backstage/plugin-catalog-backend@1.13.0-next.2 + - @backstage/plugin-badges-backend@0.2.6-next.2 + - @backstage/config@1.1.0-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35-next.2 + - @backstage/plugin-tech-insights-backend@0.5.17-next.2 + - @backstage/backend-tasks@0.5.8-next.2 + - example-app@0.2.87-next.2 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/plugin-app-backend@0.3.51-next.2 + - @backstage/plugin-auth-backend@0.18.9-next.2 + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/plugin-catalog-node@1.4.4-next.2 + - @backstage/plugin-entity-feedback-backend@0.1.9-next.2 + - @backstage/plugin-events-backend@0.2.12-next.2 + - @backstage/plugin-kubernetes-backend@0.11.6-next.2 + - @backstage/plugin-linguist-backend@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.14-next.2 + - @backstage/plugin-playlist-backend@0.3.7-next.2 + - @backstage/plugin-proxy-backend@0.3.3-next.2 + - @backstage/plugin-rollbar-backend@0.1.48-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4-next.2 + - @backstage/plugin-search-backend@1.4.3-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.7-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.7-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.12-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.2 + - @backstage/plugin-techdocs-backend@1.7.0-next.2 + - @backstage/integration@1.7.0-next.2 + - @backstage/plugin-devtools-backend@0.1.6-next.2 + - @backstage/catalog-model@1.4.2-next.1 + - @backstage/plugin-adr-backend@0.3.9-next.2 + - @backstage/plugin-azure-devops-backend@0.3.30-next.2 + - @backstage/plugin-azure-sites-backend@0.1.13-next.2 + - @backstage/plugin-explore-backend@0.0.13-next.2 + - @backstage/plugin-graphql-backend@0.1.41-next.2 + - @backstage/plugin-jenkins-backend@0.2.6-next.2 + - @backstage/plugin-kafka-backend@0.2.44-next.2 + - @backstage/plugin-lighthouse-backend@0.2.7-next.2 + - @backstage/plugin-nomad-backend@0.1.5-next.2 + - @backstage/plugin-permission-common@0.7.8-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.20-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.6-next.2 + - @backstage/plugin-search-backend-node@1.2.7-next.2 + - @backstage/plugin-tech-insights-node@0.4.9-next.2 + - @backstage/plugin-todo-backend@0.2.3-next.2 + - @backstage/catalog-client@1.4.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.2.3-next.2 + - @backstage/plugin-events-node@0.2.12-next.2 + - @backstage/plugin-search-common@1.2.6-next.1 + +## 0.2.87-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-pg@0.5.12-next.1 + - @backstage/plugin-kubernetes-backend@0.11.6-next.1 + - @backstage/plugin-catalog-backend@1.13.0-next.1 + - @backstage/plugin-auth-backend@0.18.9-next.1 + - @backstage/config@1.1.0-next.0 + - @backstage/integration@1.7.0-next.1 + - @backstage/plugin-devtools-backend@0.1.6-next.1 + - @backstage/backend-tasks@0.5.8-next.1 + - @backstage/plugin-techdocs-backend@1.7.0-next.1 + - @backstage/plugin-scaffolder-backend@1.16.6-next.1 + - @backstage/plugin-code-coverage-backend@0.2.17-next.1 + - example-app@0.2.87-next.1 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/catalog-model@1.4.2-next.0 + - @backstage/plugin-adr-backend@0.3.9-next.1 + - @backstage/plugin-app-backend@0.3.51-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + - @backstage/plugin-azure-devops-backend@0.3.30-next.1 + - @backstage/plugin-azure-sites-backend@0.1.13-next.1 + - @backstage/plugin-badges-backend@0.2.6-next.1 + - @backstage/plugin-entity-feedback-backend@0.1.9-next.1 + - @backstage/plugin-events-backend@0.2.12-next.1 + - @backstage/plugin-explore-backend@0.0.13-next.1 + - @backstage/plugin-graphql-backend@0.1.41-next.1 + - @backstage/plugin-jenkins-backend@0.2.6-next.1 + - @backstage/plugin-kafka-backend@0.2.44-next.1 + - @backstage/plugin-lighthouse-backend@0.2.7-next.1 + - @backstage/plugin-linguist-backend@0.4.3-next.1 + - @backstage/plugin-nomad-backend@0.1.5-next.1 + - @backstage/plugin-permission-backend@0.5.26-next.1 + - @backstage/plugin-permission-common@0.7.8-next.0 + - @backstage/plugin-permission-node@0.7.14-next.1 + - @backstage/plugin-playlist-backend@0.3.7-next.1 + - @backstage/plugin-proxy-backend@0.3.3-next.1 + - @backstage/plugin-rollbar-backend@0.1.48-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.20-next.1 + - @backstage/plugin-search-backend@1.4.3-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.7-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.6-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.7-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.1 + - @backstage/plugin-search-backend-node@1.2.7-next.1 + - @backstage/plugin-tech-insights-backend@0.5.17-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35-next.1 + - @backstage/plugin-tech-insights-node@0.4.9-next.1 + - @backstage/plugin-todo-backend@0.2.3-next.1 + - @backstage/plugin-catalog-node@1.4.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.2.3-next.1 + - @backstage/plugin-events-node@0.2.12-next.1 + - @backstage/catalog-client@1.4.4-next.0 + - @backstage/plugin-search-common@1.2.6-next.0 + +## 0.2.87-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.12.2-next.0 + - @backstage/plugin-auth-backend@0.18.8-next.0 + - @backstage/plugin-code-coverage-backend@0.2.16-next.0 + - @backstage/plugin-scaffolder-backend@1.16.3-next.0 + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-common@0.19.4-next.0 + - @backstage/plugin-linguist-backend@0.4.2-next.0 + - @backstage/integration@1.7.0-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.8-next.0 + - @backstage/plugin-tech-insights-backend@0.5.16-next.0 + - @backstage/backend-tasks@0.5.7-next.0 + - @backstage/plugin-app-backend@0.3.50-next.0 + - example-app@0.2.87-next.0 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/plugin-adr-backend@0.3.8-next.0 + - @backstage/plugin-azure-devops-backend@0.3.29-next.0 + - @backstage/plugin-azure-sites-backend@0.1.12-next.0 + - @backstage/plugin-badges-backend@0.2.5-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.2.2-next.0 + - @backstage/plugin-catalog-node@1.4.3-next.0 + - @backstage/plugin-devtools-backend@0.1.5-next.0 + - @backstage/plugin-events-backend@0.2.11-next.0 + - @backstage/plugin-events-node@0.2.11-next.0 + - @backstage/plugin-explore-backend@0.0.12-next.0 + - @backstage/plugin-graphql-backend@0.1.40-next.0 + - @backstage/plugin-jenkins-backend@0.2.5-next.0 + - @backstage/plugin-kafka-backend@0.2.43-next.0 + - @backstage/plugin-kubernetes-backend@0.11.5-next.0 + - @backstage/plugin-lighthouse-backend@0.2.6-next.0 + - @backstage/plugin-nomad-backend@0.1.4-next.0 + - @backstage/plugin-permission-backend@0.5.25-next.0 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.13-next.0 + - @backstage/plugin-playlist-backend@0.3.6-next.0 + - @backstage/plugin-proxy-backend@0.3.2-next.0 + - @backstage/plugin-rollbar-backend@0.1.47-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.3-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.19-next.0 + - @backstage/plugin-search-backend@1.4.2-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.5-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.11-next.0 + - @backstage/plugin-search-backend-node@1.2.6-next.0 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.34-next.0 + - @backstage/plugin-tech-insights-node@0.4.8-next.0 + - @backstage/plugin-techdocs-backend@1.6.7-next.0 + - @backstage/plugin-todo-backend@0.2.2-next.0 + +## 0.2.86 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-elasticsearch@1.3.3 + - @backstage/plugin-search-backend-module-pg@0.5.9 + - @backstage/plugin-azure-devops-backend@0.3.27 + - @backstage/plugin-kubernetes-backend@0.11.3 + - @backstage/plugin-lighthouse-backend@0.2.4 + - @backstage/plugin-permission-backend@0.5.23 + - @backstage/plugin-scaffolder-backend@1.16.0 + - @backstage/plugin-devtools-backend@0.1.3 + - @backstage/plugin-techdocs-backend@1.6.5 + - @backstage/backend-common@0.19.2 + - @backstage/plugin-catalog-backend@1.12.0 + - @backstage/plugin-badges-backend@0.2.3 + - @backstage/plugin-events-backend@0.2.9 + - @backstage/plugin-search-backend@1.4.0 + - @backstage/plugin-kafka-backend@0.2.41 + - @backstage/plugin-proxy-backend@0.3.0 + - @backstage/plugin-todo-backend@0.2.0 + - @backstage/plugin-app-backend@0.3.48 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1 + - @backstage/plugin-auth-backend@0.18.6 + - @backstage/plugin-explore-backend@0.0.10 + - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.17 + - @backstage/plugin-entity-feedback-backend@0.1.6 + - @backstage/plugin-code-coverage-backend@0.2.14 + - @backstage/plugin-search-backend-node@1.2.4 + - @backstage/plugin-linguist-backend@0.4.0 + - @backstage/plugin-playlist-backend@0.3.4 + - @backstage/plugin-jenkins-backend@0.2.3 + - @backstage/plugin-nomad-backend@0.1.2 + - @backstage/plugin-catalog-node@1.4.1 + - @backstage/plugin-events-node@0.2.9 + - @backstage/plugin-auth-node@0.2.17 + - @backstage/integration@1.6.0 + - @backstage/backend-tasks@0.5.5 + - example-app@0.2.86 + - @backstage/plugin-adr-backend@0.3.6 + - @backstage/plugin-azure-sites-backend@0.1.10 + - @backstage/plugin-graphql-backend@0.1.38 + - @backstage/plugin-permission-node@0.7.11 + - @backstage/plugin-rollbar-backend@0.1.45 + - @backstage/plugin-tech-insights-backend@0.5.14 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32 + - @backstage/plugin-tech-insights-node@0.4.6 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-search-common@1.2.5 + +## 0.2.86-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.18.6-next.2 + - @backstage/plugin-scaffolder-backend@1.15.2-next.2 + - @backstage/plugin-explore-backend@0.0.10-next.2 + - @backstage/plugin-catalog-backend@1.12.0-next.2 + - @backstage/plugin-proxy-backend@0.3.0-next.2 + - @backstage/backend-tasks@0.5.5-next.2 + - @backstage/plugin-app-backend@0.3.48-next.2 + - @backstage/plugin-linguist-backend@0.4.0-next.2 + - @backstage/plugin-techdocs-backend@1.6.5-next.2 + - @backstage/backend-common@0.19.2-next.2 + - example-app@0.2.86-next.2 + - @backstage/plugin-adr-backend@0.3.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.27-next.2 + - @backstage/plugin-badges-backend@0.2.3-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.2 + - @backstage/plugin-catalog-node@1.4.1-next.2 + - @backstage/plugin-devtools-backend@0.1.3-next.2 + - @backstage/plugin-entity-feedback-backend@0.1.6-next.2 + - @backstage/plugin-events-backend@0.2.9-next.2 + - @backstage/plugin-events-node@0.2.9-next.2 + - @backstage/plugin-kafka-backend@0.2.41-next.2 + - @backstage/plugin-kubernetes-backend@0.11.3-next.2 + - @backstage/plugin-lighthouse-backend@0.2.4-next.2 + - @backstage/plugin-permission-backend@0.5.23-next.2 + - @backstage/plugin-permission-node@0.7.11-next.2 + - @backstage/plugin-search-backend@1.4.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.9-next.2 + - @backstage/plugin-search-backend-node@1.2.4-next.2 + - @backstage/plugin-todo-backend@0.2.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.14-next.2 + - @backstage/plugin-tech-insights-node@0.4.6-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.2 + - @backstage/plugin-auth-node@0.2.17-next.2 + - @backstage/plugin-azure-sites-backend@0.1.10-next.2 + - @backstage/plugin-code-coverage-backend@0.2.14-next.2 + - @backstage/plugin-graphql-backend@0.1.38-next.2 + - @backstage/plugin-jenkins-backend@0.2.3-next.2 + - @backstage/plugin-nomad-backend@0.1.2-next.2 + - @backstage/plugin-playlist-backend@0.3.4-next.2 + - @backstage/plugin-rollbar-backend@0.1.45-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.2 + +## 0.2.86-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.9-next.1 + - @backstage/plugin-azure-devops-backend@0.3.27-next.1 + - @backstage/plugin-kubernetes-backend@0.11.3-next.1 + - @backstage/plugin-lighthouse-backend@0.2.4-next.1 + - @backstage/plugin-permission-backend@0.5.23-next.1 + - @backstage/plugin-scaffolder-backend@1.15.2-next.1 + - @backstage/plugin-devtools-backend@0.1.3-next.1 + - @backstage/plugin-techdocs-backend@1.6.5-next.1 + - @backstage/backend-common@0.19.2-next.1 + - @backstage/plugin-catalog-backend@1.12.0-next.1 + - @backstage/plugin-badges-backend@0.2.3-next.1 + - @backstage/plugin-events-backend@0.2.9-next.1 + - @backstage/plugin-search-backend@1.4.0-next.1 + - @backstage/plugin-kafka-backend@0.2.41-next.1 + - @backstage/plugin-proxy-backend@0.2.42-next.1 + - @backstage/plugin-todo-backend@0.2.0-next.1 + - @backstage/plugin-app-backend@0.3.48-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.1 + - @backstage/plugin-entity-feedback-backend@0.1.6-next.1 + - @backstage/plugin-code-coverage-backend@0.2.14-next.1 + - @backstage/plugin-search-backend-node@1.2.4-next.1 + - @backstage/plugin-linguist-backend@0.3.2-next.1 + - @backstage/plugin-playlist-backend@0.3.4-next.1 + - @backstage/plugin-explore-backend@0.0.10-next.1 + - @backstage/plugin-jenkins-backend@0.2.3-next.1 + - @backstage/plugin-nomad-backend@0.1.2-next.1 + - @backstage/plugin-catalog-node@1.4.1-next.1 + - @backstage/plugin-events-node@0.2.9-next.1 + - @backstage/plugin-auth-node@0.2.17-next.1 + - @backstage/plugin-auth-backend@0.18.6-next.1 + - @backstage/backend-tasks@0.5.5-next.1 + - @backstage/plugin-adr-backend@0.3.6-next.1 + - @backstage/plugin-azure-sites-backend@0.1.10-next.1 + - @backstage/plugin-graphql-backend@0.1.38-next.1 + - @backstage/plugin-permission-node@0.7.11-next.1 + - @backstage/plugin-rollbar-backend@0.1.45-next.1 + - @backstage/plugin-tech-insights-backend@0.5.14-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.1 + - @backstage/plugin-tech-insights-node@0.4.6-next.1 + - example-app@0.2.86-next.1 + - @backstage/integration@1.5.1 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-search-common@1.2.5 + +## 0.2.86-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-linguist-backend@0.3.2-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.0 + - @backstage/plugin-search-backend-node@1.2.4-next.0 + - @backstage/plugin-todo-backend@0.2.0-next.0 + - @backstage/plugin-catalog-backend@1.12.0-next.0 + - @backstage/plugin-search-backend@1.4.0-next.0 + - example-app@0.2.86-next.0 + - @backstage/backend-common@0.19.2-next.0 + - @backstage/backend-tasks@0.5.5-next.0 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/integration@1.5.1 + - @backstage/plugin-adr-backend@0.3.6-next.0 + - @backstage/plugin-app-backend@0.3.48-next.0 + - @backstage/plugin-auth-backend@0.18.6-next.0 + - @backstage/plugin-auth-node@0.2.17-next.0 + - @backstage/plugin-azure-devops-backend@0.3.27-next.0 + - @backstage/plugin-azure-sites-backend@0.1.10-next.0 + - @backstage/plugin-badges-backend@0.2.3-next.0 + - @backstage/plugin-catalog-node@1.4.1-next.0 + - @backstage/plugin-code-coverage-backend@0.2.14-next.0 + - @backstage/plugin-devtools-backend@0.1.3-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.6-next.0 + - @backstage/plugin-events-backend@0.2.9-next.0 + - @backstage/plugin-events-node@0.2.9-next.0 + - @backstage/plugin-explore-backend@0.0.10-next.0 + - @backstage/plugin-graphql-backend@0.1.38-next.0 + - @backstage/plugin-jenkins-backend@0.2.3-next.0 + - @backstage/plugin-kafka-backend@0.2.41-next.0 + - @backstage/plugin-kubernetes-backend@0.11.3-next.0 + - @backstage/plugin-lighthouse-backend@0.2.4-next.0 + - @backstage/plugin-nomad-backend@0.1.2-next.0 + - @backstage/plugin-permission-backend@0.5.23-next.0 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.11-next.0 + - @backstage/plugin-playlist-backend@0.3.4-next.0 + - @backstage/plugin-proxy-backend@0.2.42-next.0 + - @backstage/plugin-rollbar-backend@0.1.45-next.0 + - @backstage/plugin-scaffolder-backend@1.15.2-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.9-next.0 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-tech-insights-backend@0.5.14-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.0 + - @backstage/plugin-tech-insights-node@0.4.6-next.0 + - @backstage/plugin-techdocs-backend@1.6.5-next.0 + +## 0.2.85 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.11.2 + - @backstage/plugin-badges-backend@0.2.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.0 + - @backstage/plugin-devtools-backend@0.1.2 + - @backstage/plugin-tech-insights-backend@0.5.13 + - @backstage/backend-common@0.19.1 + - @backstage/plugin-scaffolder-backend@1.15.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1 + - @backstage/plugin-azure-devops-backend@0.3.26 + - @backstage/plugin-linguist-backend@0.3.1 + - @backstage/plugin-adr-backend@0.3.5 + - @backstage/plugin-lighthouse-backend@0.2.3 + - @backstage/plugin-entity-feedback-backend@0.1.5 + - @backstage/plugin-catalog-backend@1.11.0 + - @backstage/plugin-catalog-node@1.4.0 + - @backstage/plugin-auth-backend@0.18.5 + - example-app@0.2.85 + - @backstage/backend-tasks@0.5.4 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/config@1.0.8 + - @backstage/integration@1.5.1 + - @backstage/plugin-app-backend@0.3.47 + - @backstage/plugin-auth-node@0.2.16 + - @backstage/plugin-azure-sites-backend@0.1.9 + - @backstage/plugin-code-coverage-backend@0.2.13 + - @backstage/plugin-events-backend@0.2.8 + - @backstage/plugin-events-node@0.2.8 + - @backstage/plugin-explore-backend@0.0.9 + - @backstage/plugin-graphql-backend@0.1.37 + - @backstage/plugin-jenkins-backend@0.2.2 + - @backstage/plugin-kafka-backend@0.2.40 + - @backstage/plugin-nomad-backend@0.1.1 + - @backstage/plugin-permission-backend@0.5.22 + - @backstage/plugin-permission-common@0.7.7 + - @backstage/plugin-permission-node@0.7.10 + - @backstage/plugin-playlist-backend@0.3.3 + - @backstage/plugin-proxy-backend@0.2.41 + - @backstage/plugin-rollbar-backend@0.1.44 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.16 + - @backstage/plugin-search-backend@1.3.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.2 + - @backstage/plugin-search-backend-module-pg@0.5.8 + - @backstage/plugin-search-backend-node@1.2.3 + - @backstage/plugin-search-common@1.2.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.31 + - @backstage/plugin-tech-insights-node@0.4.5 + - @backstage/plugin-techdocs-backend@1.6.4 + - @backstage/plugin-todo-backend@0.1.44 + +## 0.2.85-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.0-next.1 + - @backstage/plugin-devtools-backend@0.1.2-next.2 + - @backstage/plugin-tech-insights-backend@0.5.13-next.1 + - @backstage/plugin-scaffolder-backend@1.15.1-next.1 + - @backstage/plugin-kubernetes-backend@0.11.2-next.2 + - @backstage/plugin-adr-backend@0.3.5-next.1 + - example-app@0.2.85-next.2 + - @backstage/backend-common@0.19.1-next.0 + - @backstage/backend-tasks@0.5.4-next.0 + - @backstage/catalog-client@1.4.3-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/config@1.0.8 + - @backstage/integration@1.5.1-next.0 + - @backstage/plugin-app-backend@0.3.47-next.0 + - @backstage/plugin-auth-backend@0.18.5-next.1 + - @backstage/plugin-auth-node@0.2.16-next.0 + - @backstage/plugin-azure-devops-backend@0.3.26-next.1 + - @backstage/plugin-azure-sites-backend@0.1.9-next.0 + - @backstage/plugin-badges-backend@0.2.2-next.1 + - @backstage/plugin-catalog-backend@1.11.0-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1-next.0 + - @backstage/plugin-catalog-node@1.4.0-next.0 + - @backstage/plugin-code-coverage-backend@0.2.13-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.5-next.0 + - @backstage/plugin-events-backend@0.2.8-next.0 + - @backstage/plugin-events-node@0.2.8-next.0 + - @backstage/plugin-explore-backend@0.0.9-next.0 + - @backstage/plugin-graphql-backend@0.1.37-next.0 + - @backstage/plugin-jenkins-backend@0.2.2-next.0 + - @backstage/plugin-kafka-backend@0.2.40-next.0 + - @backstage/plugin-lighthouse-backend@0.2.3-next.0 + - @backstage/plugin-linguist-backend@0.3.1-next.1 + - @backstage/plugin-nomad-backend@0.1.1-next.0 + - @backstage/plugin-permission-backend@0.5.22-next.0 + - @backstage/plugin-permission-common@0.7.7-next.0 + - @backstage/plugin-permission-node@0.7.10-next.0 + - @backstage/plugin-playlist-backend@0.3.3-next.0 + - @backstage/plugin-proxy-backend@0.2.41-next.0 + - @backstage/plugin-rollbar-backend@0.1.44-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.16-next.1 + - @backstage/plugin-search-backend@1.3.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.2-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.8-next.0 + - @backstage/plugin-search-backend-node@1.2.3-next.0 + - @backstage/plugin-search-common@1.2.5-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.31-next.0 + - @backstage/plugin-tech-insights-node@0.4.5-next.0 + - @backstage/plugin-techdocs-backend@1.6.4-next.0 + - @backstage/plugin-todo-backend@0.1.44-next.0 + +## 0.2.85-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.11.2-next.1 + - @backstage/plugin-badges-backend@0.2.2-next.1 + - @backstage/plugin-azure-devops-backend@0.3.26-next.1 + - @backstage/plugin-devtools-backend@0.1.2-next.1 + - @backstage/plugin-linguist-backend@0.3.1-next.1 + - @backstage/plugin-auth-backend@0.18.5-next.1 + - example-app@0.2.85-next.1 + - @backstage/config@1.0.8 + +## 0.2.85-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.1-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.5-next.0 + - @backstage/plugin-catalog-backend@1.11.0-next.0 + - @backstage/plugin-catalog-node@1.4.0-next.0 + - @backstage/plugin-kubernetes-backend@0.11.2-next.0 + - example-app@0.2.85-next.0 + - @backstage/backend-tasks@0.5.4-next.0 + - @backstage/catalog-client@1.4.3-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/config@1.0.8 + - @backstage/integration@1.5.1-next.0 + - @backstage/plugin-adr-backend@0.3.5-next.0 + - @backstage/plugin-app-backend@0.3.47-next.0 + - @backstage/plugin-auth-backend@0.18.5-next.0 + - @backstage/plugin-auth-node@0.2.16-next.0 + - @backstage/plugin-azure-devops-backend@0.3.26-next.0 + - @backstage/plugin-azure-sites-backend@0.1.9-next.0 + - @backstage/plugin-badges-backend@0.2.2-next.0 + - @backstage/plugin-code-coverage-backend@0.2.13-next.0 + - @backstage/plugin-devtools-backend@0.1.2-next.0 + - @backstage/plugin-events-backend@0.2.8-next.0 + - @backstage/plugin-events-node@0.2.8-next.0 + - @backstage/plugin-explore-backend@0.0.9-next.0 + - @backstage/plugin-graphql-backend@0.1.37-next.0 + - @backstage/plugin-jenkins-backend@0.2.2-next.0 + - @backstage/plugin-kafka-backend@0.2.40-next.0 + - @backstage/plugin-lighthouse-backend@0.2.3-next.0 + - @backstage/plugin-linguist-backend@0.3.1-next.0 + - @backstage/plugin-nomad-backend@0.1.1-next.0 + - @backstage/plugin-permission-backend@0.5.22-next.0 + - @backstage/plugin-permission-common@0.7.7-next.0 + - @backstage/plugin-permission-node@0.7.10-next.0 + - @backstage/plugin-playlist-backend@0.3.3-next.0 + - @backstage/plugin-proxy-backend@0.2.41-next.0 + - @backstage/plugin-rollbar-backend@0.1.44-next.0 + - @backstage/plugin-scaffolder-backend@1.15.1-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.4-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.16-next.0 + - @backstage/plugin-search-backend@1.3.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.2-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.8-next.0 + - @backstage/plugin-search-backend-node@1.2.3-next.0 + - @backstage/plugin-search-common@1.2.5-next.0 + - @backstage/plugin-tech-insights-backend@0.5.13-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.31-next.0 + - @backstage/plugin-tech-insights-node@0.4.5-next.0 + - @backstage/plugin-techdocs-backend@1.6.4-next.0 + - @backstage/plugin-todo-backend@0.1.44-next.0 + +## 0.2.84 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/catalog-client@1.4.2 + - @backstage/plugin-jenkins-backend@0.2.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3 + - @backstage/plugin-devtools-backend@0.1.1 + - @backstage/plugin-scaffolder-backend@1.15.0 + - @backstage/plugin-nomad-backend@0.1.0 + - @backstage/plugin-azure-sites-backend@0.1.8 + - @backstage/plugin-kubernetes-backend@0.11.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0 + - @backstage/plugin-badges-backend@0.2.1 + - @backstage/plugin-catalog-backend@1.10.0 + - @backstage/integration@1.5.0 + - @backstage/plugin-search-backend@1.3.2 + - @backstage/plugin-explore-backend@0.0.8 + - @backstage/catalog-model@1.4.0 + - @backstage/plugin-auth-backend@0.18.4 + - @backstage/plugin-adr-backend@0.3.4 + - @backstage/plugin-code-coverage-backend@0.2.12 + - @backstage/plugin-proxy-backend@0.2.40 + - @backstage/plugin-linguist-backend@0.3.0 + - @backstage/plugin-search-backend-module-pg@0.5.7 + - example-app@0.2.84 + - @backstage/backend-tasks@0.5.3 + - @backstage/plugin-app-backend@0.3.46 + - @backstage/plugin-auth-node@0.2.15 + - @backstage/plugin-azure-devops-backend@0.3.25 + - @backstage/plugin-catalog-node@1.3.7 + - @backstage/plugin-entity-feedback-backend@0.1.4 + - @backstage/plugin-events-backend@0.2.7 + - @backstage/plugin-graphql-backend@0.1.36 + - @backstage/plugin-kafka-backend@0.2.39 + - @backstage/plugin-lighthouse-backend@0.2.2 + - @backstage/plugin-permission-backend@0.5.21 + - @backstage/plugin-permission-node@0.7.9 + - @backstage/plugin-playlist-backend@0.3.2 + - @backstage/plugin-rollbar-backend@0.1.43 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.15 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.1 + - @backstage/plugin-search-backend-node@1.2.2 + - @backstage/plugin-tech-insights-backend@0.5.12 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30 + - @backstage/plugin-tech-insights-node@0.4.4 + - @backstage/plugin-techdocs-backend@1.6.3 + - @backstage/plugin-todo-backend@0.1.43 + - @backstage/config@1.0.8 + - @backstage/plugin-events-node@0.2.7 + - @backstage/plugin-permission-common@0.7.6 + - @backstage/plugin-search-common@1.2.4 + +## 0.2.84-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.15.0-next.3 + - @backstage/plugin-kubernetes-backend@0.11.1-next.3 + - @backstage/backend-common@0.19.0-next.2 + - @backstage/catalog-model@1.4.0-next.1 + - @backstage/plugin-catalog-backend@1.10.0-next.2 + - example-app@0.2.84-next.3 + - @backstage/backend-tasks@0.5.3-next.2 + - @backstage/catalog-client@1.4.2-next.2 + - @backstage/config@1.0.7 + - @backstage/integration@1.5.0-next.0 + - @backstage/plugin-adr-backend@0.3.4-next.2 + - @backstage/plugin-app-backend@0.3.46-next.2 + - @backstage/plugin-auth-backend@0.18.4-next.3 + - @backstage/plugin-auth-node@0.2.15-next.2 + - @backstage/plugin-azure-devops-backend@0.3.25-next.2 + - @backstage/plugin-azure-sites-backend@0.1.8-next.2 + - @backstage/plugin-badges-backend@0.2.1-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0-next.1 + - @backstage/plugin-catalog-node@1.3.7-next.2 + - @backstage/plugin-code-coverage-backend@0.2.12-next.3 + - @backstage/plugin-devtools-backend@0.1.1-next.2 + - @backstage/plugin-entity-feedback-backend@0.1.4-next.2 + - @backstage/plugin-events-backend@0.2.7-next.2 + - @backstage/plugin-events-node@0.2.7-next.2 + - @backstage/plugin-explore-backend@0.0.8-next.2 + - @backstage/plugin-graphql-backend@0.1.36-next.2 + - @backstage/plugin-jenkins-backend@0.2.1-next.2 + - @backstage/plugin-kafka-backend@0.2.39-next.2 + - @backstage/plugin-lighthouse-backend@0.2.2-next.2 + - @backstage/plugin-linguist-backend@0.3.0-next.2 + - @backstage/plugin-permission-backend@0.5.21-next.2 + - @backstage/plugin-permission-common@0.7.6-next.0 + - @backstage/plugin-permission-node@0.7.9-next.2 + - @backstage/plugin-playlist-backend@0.3.2-next.2 + - @backstage/plugin-proxy-backend@0.2.40-next.2 + - @backstage/plugin-rollbar-backend@0.1.43-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.3 + - @backstage/plugin-search-backend@1.3.2-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.1-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.7-next.2 + - @backstage/plugin-search-backend-node@1.2.2-next.2 + - @backstage/plugin-search-common@1.2.4-next.0 + - @backstage/plugin-tech-insights-backend@0.5.12-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30-next.2 + - @backstage/plugin-tech-insights-node@0.4.4-next.2 + - @backstage/plugin-techdocs-backend@1.6.3-next.2 + - @backstage/plugin-todo-backend@0.1.43-next.2 + +## 0.2.84-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.11.1-next.2 + - @backstage/plugin-badges-backend@0.2.1-next.2 + - @backstage/plugin-scaffolder-backend@1.15.0-next.2 + - @backstage/plugin-auth-backend@0.18.4-next.2 + - @backstage/plugin-code-coverage-backend@0.2.12-next.2 + - example-app@0.2.84-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.2 + - @backstage/config@1.0.7 + +## 0.2.84-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/plugin-jenkins-backend@0.2.1-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3-next.1 + - @backstage/plugin-devtools-backend@0.1.1-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0-next.0 + - @backstage/plugin-catalog-backend@1.9.2-next.1 + - @backstage/integration@1.5.0-next.0 + - @backstage/plugin-adr-backend@0.3.4-next.1 + - @backstage/plugin-proxy-backend@0.2.40-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.7-next.1 + - @backstage/catalog-model@1.4.0-next.0 + - @backstage/plugin-scaffolder-backend@1.15.0-next.1 + - @backstage/backend-tasks@0.5.3-next.1 + - @backstage/plugin-app-backend@0.3.46-next.1 + - @backstage/plugin-auth-backend@0.18.4-next.1 + - @backstage/plugin-auth-node@0.2.15-next.1 + - @backstage/plugin-azure-devops-backend@0.3.25-next.1 + - @backstage/plugin-azure-sites-backend@0.1.8-next.1 + - @backstage/plugin-badges-backend@0.2.1-next.1 + - @backstage/plugin-catalog-node@1.3.7-next.1 + - @backstage/plugin-code-coverage-backend@0.2.12-next.1 + - @backstage/plugin-entity-feedback-backend@0.1.4-next.1 + - @backstage/plugin-events-backend@0.2.7-next.1 + - @backstage/plugin-explore-backend@0.0.8-next.1 + - @backstage/plugin-graphql-backend@0.1.36-next.1 + - @backstage/plugin-kafka-backend@0.2.39-next.1 + - @backstage/plugin-kubernetes-backend@0.11.1-next.1 + - @backstage/plugin-lighthouse-backend@0.2.2-next.1 + - @backstage/plugin-linguist-backend@0.3.0-next.1 + - @backstage/plugin-permission-backend@0.5.21-next.1 + - @backstage/plugin-permission-node@0.7.9-next.1 + - @backstage/plugin-playlist-backend@0.3.2-next.1 + - @backstage/plugin-rollbar-backend@0.1.43-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.1 + - @backstage/plugin-search-backend@1.3.2-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.1-next.1 + - @backstage/plugin-search-backend-node@1.2.2-next.1 + - @backstage/plugin-tech-insights-backend@0.5.12-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30-next.1 + - @backstage/plugin-tech-insights-node@0.4.4-next.1 + - @backstage/plugin-techdocs-backend@1.6.3-next.1 + - @backstage/plugin-todo-backend@0.1.43-next.1 + - example-app@0.2.84-next.1 + - @backstage/catalog-client@1.4.2-next.1 + - @backstage/plugin-permission-common@0.7.6-next.0 + - @backstage/plugin-events-node@0.2.7-next.1 + - @backstage/config@1.0.7 + - @backstage/plugin-search-common@1.2.4-next.0 + +## 0.2.84-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.4.2-next.0 + - @backstage/plugin-scaffolder-backend@1.14.1-next.0 + - @backstage/plugin-jenkins-backend@0.2.1-next.0 + - @backstage/plugin-linguist-backend@0.3.0-next.0 + - @backstage/plugin-devtools-backend@0.1.1-next.0 + - @backstage/plugin-adr-backend@0.3.4-next.0 + - @backstage/plugin-auth-backend@0.18.4-next.0 + - @backstage/plugin-badges-backend@0.2.1-next.0 + - @backstage/plugin-catalog-backend@1.9.2-next.0 + - @backstage/plugin-catalog-node@1.3.7-next.0 + - @backstage/plugin-code-coverage-backend@0.2.12-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.4-next.0 + - @backstage/plugin-kubernetes-backend@0.11.1-next.0 + - @backstage/plugin-lighthouse-backend@0.2.2-next.0 + - @backstage/plugin-playlist-backend@0.3.2-next.0 + - @backstage/plugin-tech-insights-backend@0.5.12-next.0 + - @backstage/plugin-techdocs-backend@1.6.3-next.0 + - @backstage/plugin-todo-backend@0.1.43-next.0 + - example-app@0.2.84-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.0 + - @backstage/backend-common@0.18.6-next.0 + - @backstage/integration@1.4.5 + - @backstage/plugin-app-backend@0.3.46-next.0 + - @backstage/plugin-explore-backend@0.0.8-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-tasks@0.5.3-next.0 + - @backstage/catalog-model@1.3.0 + - @backstage/plugin-auth-node@0.2.15-next.0 + - @backstage/plugin-azure-devops-backend@0.3.25-next.0 + - @backstage/plugin-azure-sites-backend@0.1.8-next.0 + - @backstage/plugin-events-backend@0.2.7-next.0 + - @backstage/plugin-events-node@0.2.7-next.0 + - @backstage/plugin-graphql-backend@0.1.36-next.0 + - @backstage/plugin-kafka-backend@0.2.39-next.0 + - @backstage/plugin-permission-backend@0.5.21-next.0 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-permission-node@0.7.9-next.0 + - @backstage/plugin-proxy-backend@0.2.40-next.0 + - @backstage/plugin-rollbar-backend@0.1.43-next.0 + - @backstage/plugin-search-backend@1.3.2-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.1-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.7-next.0 + - @backstage/plugin-search-backend-node@1.2.2-next.0 + - @backstage/plugin-search-common@1.2.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30-next.0 + - @backstage/plugin-tech-insights-node@0.4.4-next.0 + +## 0.2.83 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.14.0 + - @backstage/plugin-devtools-backend@0.1.0 + - @backstage/plugin-catalog-backend@1.9.1 + - @backstage/backend-common@0.18.5 + - @backstage/plugin-kubernetes-backend@0.11.0 + - @backstage/plugin-auth-backend@0.18.3 + - @backstage/plugin-badges-backend@0.2.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.0 + - @backstage/integration@1.4.5 + - @backstage/plugin-todo-backend@0.1.42 + - @backstage/plugin-jenkins-backend@0.2.0 + - @backstage/plugin-azure-sites-backend@0.1.7 + - @backstage/plugin-permission-node@0.7.8 + - @backstage/plugin-search-backend@1.3.1 + - example-app@0.2.83 + - @backstage/backend-tasks@0.5.2 + - @backstage/plugin-app-backend@0.3.45 + - @backstage/plugin-auth-node@0.2.14 + - @backstage/plugin-catalog-node@1.3.6 + - @backstage/plugin-entity-feedback-backend@0.1.3 + - @backstage/plugin-events-backend@0.2.6 + - @backstage/plugin-playlist-backend@0.3.1 + - @backstage/plugin-rollbar-backend@0.1.42 + - @backstage/plugin-search-backend-module-pg@0.5.6 + - @backstage/plugin-tech-insights-backend@0.5.11 + - @backstage/plugin-techdocs-backend@1.6.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.14 + - @backstage/plugin-adr-backend@0.3.3 + - @backstage/plugin-azure-devops-backend@0.3.24 + - @backstage/plugin-code-coverage-backend@0.2.11 + - @backstage/plugin-explore-backend@0.0.7 + - @backstage/plugin-graphql-backend@0.1.35 + - @backstage/plugin-kafka-backend@0.2.38 + - @backstage/plugin-lighthouse-backend@0.2.1 + - @backstage/plugin-linguist-backend@0.2.2 + - @backstage/plugin-permission-backend@0.5.20 + - @backstage/plugin-proxy-backend@0.2.39 + - @backstage/plugin-search-backend-node@1.2.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.29 + - @backstage/plugin-tech-insights-node@0.4.3 + - @backstage/catalog-client@1.4.1 + - @backstage/catalog-model@1.3.0 + - @backstage/config@1.0.7 + - @backstage/plugin-events-node@0.2.6 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-search-common@1.2.3 + +## 0.2.83-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-devtools-backend@0.1.0-next.0 + - @backstage/plugin-catalog-backend@1.9.1-next.2 + - @backstage/plugin-badges-backend@0.2.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.0-next.2 + - @backstage/plugin-kubernetes-backend@0.11.0-next.2 + - @backstage/plugin-auth-backend@0.18.3-next.2 + - @backstage/plugin-search-backend@1.3.1-next.2 + - example-app@0.2.83-next.2 + - @backstage/plugin-scaffolder-backend@1.13.2-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.2 + - @backstage/config@1.0.7 + +## 0.2.83-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/plugin-kubernetes-backend@0.11.0-next.1 + - @backstage/plugin-catalog-backend@1.9.1-next.1 + - @backstage/plugin-jenkins-backend@0.1.35-next.1 + - @backstage/plugin-scaffolder-backend@1.13.2-next.1 + - example-app@0.2.83-next.1 + - @backstage/backend-tasks@0.5.2-next.1 + - @backstage/plugin-adr-backend@0.3.3-next.1 + - @backstage/plugin-app-backend@0.3.45-next.1 + - @backstage/plugin-auth-backend@0.18.3-next.1 + - @backstage/plugin-auth-node@0.2.14-next.1 + - @backstage/plugin-azure-devops-backend@0.3.24-next.1 + - @backstage/plugin-azure-sites-backend@0.1.7-next.1 + - @backstage/plugin-badges-backend@0.1.39-next.1 + - @backstage/plugin-catalog-node@1.3.6-next.1 + - @backstage/plugin-code-coverage-backend@0.2.11-next.1 + - @backstage/plugin-entity-feedback-backend@0.1.3-next.1 + - @backstage/plugin-events-backend@0.2.6-next.1 + - @backstage/plugin-explore-backend@0.0.7-next.1 + - @backstage/plugin-graphql-backend@0.1.35-next.1 + - @backstage/plugin-kafka-backend@0.2.38-next.1 + - @backstage/plugin-lighthouse-backend@0.2.1-next.1 + - @backstage/plugin-linguist-backend@0.2.2-next.1 + - @backstage/plugin-permission-backend@0.5.20-next.1 + - @backstage/plugin-permission-node@0.7.8-next.1 + - @backstage/plugin-playlist-backend@0.3.1-next.1 + - @backstage/plugin-proxy-backend@0.2.39-next.1 + - @backstage/plugin-rollbar-backend@0.1.42-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.1 + - @backstage/plugin-search-backend@1.3.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.2.1-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.6-next.1 + - @backstage/plugin-search-backend-node@1.2.1-next.1 + - @backstage/plugin-tech-insights-backend@0.5.11-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.29-next.1 + - @backstage/plugin-tech-insights-node@0.4.3-next.1 + - @backstage/plugin-techdocs-backend@1.6.2-next.1 + - @backstage/plugin-todo-backend@0.1.42-next.1 + - @backstage/config@1.0.7 + - @backstage/plugin-events-node@0.2.6-next.1 + +## 0.2.83-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/integration@1.4.5-next.0 + - @backstage/plugin-permission-node@0.7.8-next.0 + - @backstage/plugin-scaffolder-backend@1.13.2-next.0 + - @backstage/plugin-kubernetes-backend@0.11.0-next.0 + - @backstage/backend-tasks@0.5.2-next.0 + - @backstage/plugin-app-backend@0.3.45-next.0 + - @backstage/plugin-auth-backend@0.18.3-next.0 + - @backstage/plugin-auth-node@0.2.14-next.0 + - @backstage/plugin-catalog-backend@1.9.1-next.0 + - @backstage/plugin-catalog-node@1.3.6-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.3-next.0 + - @backstage/plugin-events-backend@0.2.6-next.0 + - @backstage/plugin-playlist-backend@0.3.1-next.0 + - @backstage/plugin-rollbar-backend@0.1.42-next.0 + - @backstage/plugin-search-backend@1.3.1-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.6-next.0 + - @backstage/plugin-tech-insights-backend@0.5.11-next.0 + - @backstage/plugin-techdocs-backend@1.6.2-next.0 + - example-app@0.2.83-next.0 + - @backstage/plugin-adr-backend@0.3.3-next.0 + - @backstage/plugin-azure-devops-backend@0.3.24-next.0 + - @backstage/plugin-azure-sites-backend@0.1.7-next.0 + - @backstage/plugin-badges-backend@0.1.39-next.0 + - @backstage/plugin-code-coverage-backend@0.2.11-next.0 + - @backstage/plugin-explore-backend@0.0.7-next.0 + - @backstage/plugin-graphql-backend@0.1.35-next.0 + - @backstage/plugin-jenkins-backend@0.1.35-next.0 + - @backstage/plugin-kafka-backend@0.2.38-next.0 + - @backstage/plugin-lighthouse-backend@0.2.1-next.0 + - @backstage/plugin-linguist-backend@0.2.2-next.0 + - @backstage/plugin-permission-backend@0.5.20-next.0 + - @backstage/plugin-proxy-backend@0.2.39-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.2.1-next.0 + - @backstage/plugin-search-backend-node@1.2.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.29-next.0 + - @backstage/plugin-tech-insights-node@0.4.3-next.0 + - @backstage/plugin-todo-backend@0.1.42-next.0 + - @backstage/catalog-client@1.4.1 + - @backstage/catalog-model@1.3.0 + - @backstage/config@1.0.7 + - @backstage/plugin-events-node@0.2.6-next.0 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-search-common@1.2.3 + +## 0.2.82 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.10.0 + - @backstage/backend-common@0.18.4 + - @backstage/plugin-scaffolder-backend@1.13.0 + - @backstage/plugin-catalog-backend@1.9.0 + - @backstage/plugin-code-coverage-backend@0.2.10 + - @backstage/catalog-client@1.4.1 + - @backstage/plugin-permission-node@0.7.7 + - @backstage/plugin-permission-backend@0.5.19 + - @backstage/plugin-entity-feedback-backend@0.1.2 + - @backstage/plugin-rollbar-backend@0.1.41 + - @backstage/plugin-search-backend@1.3.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.2.0 + - @backstage/plugin-search-backend-module-pg@0.5.5 + - @backstage/plugin-auth-backend@0.18.2 + - @backstage/plugin-lighthouse-backend@0.2.0 + - @backstage/plugin-permission-common@0.7.5 + - @backstage/plugin-playlist-backend@0.3.0 + - @backstage/backend-tasks@0.5.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28 + - @backstage/plugin-adr-backend@0.3.2 + - @backstage/plugin-graphql-backend@0.1.34 + - @backstage/catalog-model@1.3.0 + - @backstage/plugin-techdocs-backend@1.6.1 + - @backstage/plugin-explore-backend@0.0.6 + - @backstage/plugin-events-backend@0.2.5 + - @backstage/plugin-search-backend-node@1.2.0 + - @backstage/integration@1.4.4 + - example-app@0.2.82 + - @backstage/plugin-app-backend@0.3.44 + - @backstage/plugin-auth-node@0.2.13 + - @backstage/plugin-azure-devops-backend@0.3.23 + - @backstage/plugin-azure-sites-backend@0.1.6 + - @backstage/plugin-badges-backend@0.1.38 + - @backstage/plugin-catalog-node@1.3.5 + - @backstage/plugin-jenkins-backend@0.1.34 + - @backstage/plugin-kafka-backend@0.2.37 + - @backstage/plugin-linguist-backend@0.2.1 + - @backstage/plugin-proxy-backend@0.2.38 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.12 + - @backstage/plugin-tech-insights-backend@0.5.10 + - @backstage/plugin-tech-insights-node@0.4.2 + - @backstage/plugin-todo-backend@0.1.41 + - @backstage/config@1.0.7 + - @backstage/plugin-events-node@0.2.5 + - @backstage/plugin-search-common@1.2.3 + +## 0.2.82-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.10.0-next.3 + - @backstage/plugin-catalog-backend@1.9.0-next.3 + - @backstage/plugin-code-coverage-backend@0.2.10-next.3 + - @backstage/plugin-lighthouse-backend@0.2.0-next.3 + - @backstage/plugin-auth-backend@0.18.2-next.3 + - @backstage/plugin-scaffolder-backend@1.13.0-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.2.0-next.3 + - @backstage/catalog-model@1.3.0-next.0 + - @backstage/plugin-events-backend@0.2.5-next.3 + - example-app@0.2.82-next.3 + - @backstage/backend-common@0.18.4-next.2 + - @backstage/backend-tasks@0.5.1-next.2 + - @backstage/catalog-client@1.4.1-next.1 + - @backstage/config@1.0.7 + - @backstage/integration@1.4.4-next.0 + - @backstage/plugin-adr-backend@0.3.2-next.3 + - @backstage/plugin-app-backend@0.3.44-next.2 + - @backstage/plugin-auth-node@0.2.13-next.2 + - @backstage/plugin-azure-devops-backend@0.3.23-next.2 + - @backstage/plugin-azure-sites-backend@0.1.6-next.2 + - @backstage/plugin-badges-backend@0.1.38-next.3 + - @backstage/plugin-catalog-node@1.3.5-next.3 + - @backstage/plugin-entity-feedback-backend@0.1.2-next.3 + - @backstage/plugin-events-node@0.2.5-next.2 + - @backstage/plugin-explore-backend@0.0.6-next.2 + - @backstage/plugin-graphql-backend@0.1.34-next.3 + - @backstage/plugin-jenkins-backend@0.1.34-next.3 + - @backstage/plugin-kafka-backend@0.2.37-next.3 + - @backstage/plugin-linguist-backend@0.2.1-next.3 + - @backstage/plugin-permission-backend@0.5.19-next.2 + - @backstage/plugin-permission-common@0.7.5-next.0 + - @backstage/plugin-permission-node@0.7.7-next.2 + - @backstage/plugin-playlist-backend@0.2.7-next.3 + - @backstage/plugin-proxy-backend@0.2.38-next.2 + - @backstage/plugin-rollbar-backend@0.1.41-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.3 + - @backstage/plugin-search-backend@1.3.0-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.5-next.2 + - @backstage/plugin-search-backend-node@1.2.0-next.2 + - @backstage/plugin-search-common@1.2.3-next.0 + - @backstage/plugin-tech-insights-backend@0.5.10-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.2 + - @backstage/plugin-tech-insights-node@0.4.2-next.2 + - @backstage/plugin-techdocs-backend@1.6.1-next.3 + - @backstage/plugin-todo-backend@0.1.41-next.3 + +## 0.2.82-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.10.0-next.2 + - @backstage/plugin-catalog-backend@1.8.1-next.2 + - @backstage/backend-common@0.18.4-next.2 + - @backstage/catalog-client@1.4.1-next.0 + - @backstage/plugin-permission-node@0.7.7-next.2 + - @backstage/plugin-permission-backend@0.5.19-next.2 + - @backstage/plugin-rollbar-backend@0.1.41-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.2 + - @backstage/plugin-scaffolder-backend@1.13.0-next.2 + - example-app@0.2.82-next.2 + - @backstage/backend-tasks@0.5.1-next.2 + - @backstage/catalog-model@1.2.1 + - @backstage/config@1.0.7 + - @backstage/integration@1.4.4-next.0 + - @backstage/plugin-adr-backend@0.3.2-next.2 + - @backstage/plugin-app-backend@0.3.44-next.2 + - @backstage/plugin-auth-backend@0.18.2-next.2 + - @backstage/plugin-auth-node@0.2.13-next.2 + - @backstage/plugin-azure-devops-backend@0.3.23-next.2 + - @backstage/plugin-azure-sites-backend@0.1.6-next.2 + - @backstage/plugin-badges-backend@0.1.38-next.2 + - @backstage/plugin-catalog-node@1.3.5-next.2 + - @backstage/plugin-code-coverage-backend@0.2.10-next.2 + - @backstage/plugin-entity-feedback-backend@0.1.2-next.2 + - @backstage/plugin-events-backend@0.2.5-next.2 + - @backstage/plugin-events-node@0.2.5-next.2 + - @backstage/plugin-explore-backend@0.0.6-next.2 + - @backstage/plugin-graphql-backend@0.1.34-next.2 + - @backstage/plugin-jenkins-backend@0.1.34-next.2 + - @backstage/plugin-kafka-backend@0.2.37-next.2 + - @backstage/plugin-lighthouse-backend@0.1.2-next.2 + - @backstage/plugin-linguist-backend@0.2.1-next.2 + - @backstage/plugin-permission-common@0.7.5-next.0 + - @backstage/plugin-playlist-backend@0.2.7-next.2 + - @backstage/plugin-proxy-backend@0.2.38-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.2 + - @backstage/plugin-search-backend@1.3.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.2.0-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.5-next.2 + - @backstage/plugin-search-backend-node@1.2.0-next.2 + - @backstage/plugin-search-common@1.2.3-next.0 + - @backstage/plugin-tech-insights-backend@0.5.10-next.2 + - @backstage/plugin-tech-insights-node@0.4.2-next.2 + - @backstage/plugin-techdocs-backend@1.6.1-next.2 + - @backstage/plugin-todo-backend@0.1.41-next.2 + +## 0.2.82-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend@1.3.0-next.1 + - @backstage/plugin-scaffolder-backend@1.13.0-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.2.0-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.5-next.1 + - @backstage/plugin-permission-node@0.7.7-next.1 + - @backstage/plugin-permission-backend@0.5.19-next.1 + - @backstage/plugin-permission-common@0.7.5-next.0 + - @backstage/plugin-playlist-backend@0.2.7-next.1 + - @backstage/plugin-catalog-backend@1.8.1-next.1 + - @backstage/backend-tasks@0.5.1-next.1 + - @backstage/plugin-adr-backend@0.3.2-next.1 + - @backstage/plugin-kubernetes-backend@0.10.0-next.1 + - @backstage/plugin-techdocs-backend@1.6.1-next.1 + - @backstage/plugin-explore-backend@0.0.6-next.1 + - @backstage/plugin-search-backend-node@1.2.0-next.1 + - @backstage/integration@1.4.4-next.0 + - @backstage/plugin-auth-backend@0.18.2-next.1 + - example-app@0.2.82-next.1 + - @backstage/backend-common@0.18.4-next.1 + - @backstage/catalog-client@1.4.0 + - @backstage/catalog-model@1.2.1 + - @backstage/config@1.0.7 + - @backstage/plugin-app-backend@0.3.44-next.1 + - @backstage/plugin-auth-node@0.2.13-next.1 + - @backstage/plugin-azure-devops-backend@0.3.23-next.1 + - @backstage/plugin-azure-sites-backend@0.1.6-next.1 + - @backstage/plugin-badges-backend@0.1.38-next.1 + - @backstage/plugin-catalog-node@1.3.5-next.1 + - @backstage/plugin-code-coverage-backend@0.2.10-next.1 + - @backstage/plugin-entity-feedback-backend@0.1.2-next.1 + - @backstage/plugin-events-backend@0.2.5-next.1 + - @backstage/plugin-events-node@0.2.5-next.1 + - @backstage/plugin-graphql-backend@0.1.34-next.1 + - @backstage/plugin-jenkins-backend@0.1.34-next.1 + - @backstage/plugin-kafka-backend@0.2.37-next.1 + - @backstage/plugin-lighthouse-backend@0.1.2-next.1 + - @backstage/plugin-linguist-backend@0.2.1-next.1 + - @backstage/plugin-proxy-backend@0.2.38-next.1 + - @backstage/plugin-rollbar-backend@0.1.41-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.1 + - @backstage/plugin-search-common@1.2.3-next.0 + - @backstage/plugin-tech-insights-backend@0.5.10-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.1 + - @backstage/plugin-tech-insights-node@0.4.2-next.1 + - @backstage/plugin-todo-backend@0.1.41-next.1 + +## 0.2.82-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.12.1-next.0 + - @backstage/plugin-kubernetes-backend@0.10.0-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.2-next.0 + - @backstage/plugin-auth-backend@0.18.2-next.0 + - @backstage/plugin-catalog-backend@1.8.1-next.0 + - @backstage/plugin-graphql-backend@0.1.34-next.0 + - @backstage/backend-common@0.18.4-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.0 + - example-app@0.2.82-next.0 + - @backstage/config@1.0.7 + - @backstage/integration@1.4.3 + - @backstage/backend-tasks@0.5.1-next.0 + - @backstage/catalog-client@1.4.0 + - @backstage/catalog-model@1.2.1 + - @backstage/plugin-adr-backend@0.3.2-next.0 + - @backstage/plugin-app-backend@0.3.44-next.0 + - @backstage/plugin-auth-node@0.2.13-next.0 + - @backstage/plugin-azure-devops-backend@0.3.23-next.0 + - @backstage/plugin-azure-sites-backend@0.1.6-next.0 + - @backstage/plugin-badges-backend@0.1.38-next.0 + - @backstage/plugin-catalog-node@1.3.5-next.0 + - @backstage/plugin-code-coverage-backend@0.2.10-next.0 + - @backstage/plugin-events-backend@0.2.5-next.0 + - @backstage/plugin-events-node@0.2.5-next.0 + - @backstage/plugin-explore-backend@0.0.6-next.0 + - @backstage/plugin-jenkins-backend@0.1.34-next.0 + - @backstage/plugin-kafka-backend@0.2.37-next.0 + - @backstage/plugin-lighthouse-backend@0.1.2-next.0 + - @backstage/plugin-linguist-backend@0.2.1-next.0 + - @backstage/plugin-permission-backend@0.5.19-next.0 + - @backstage/plugin-permission-common@0.7.4 + - @backstage/plugin-permission-node@0.7.7-next.0 + - @backstage/plugin-playlist-backend@0.2.7-next.0 + - @backstage/plugin-proxy-backend@0.2.38-next.0 + - @backstage/plugin-rollbar-backend@0.1.41-next.0 + - @backstage/plugin-search-backend@1.2.5-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.5-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.5-next.0 + - @backstage/plugin-search-backend-node@1.1.5-next.0 + - @backstage/plugin-search-common@1.2.2 + - @backstage/plugin-tech-insights-backend@0.5.10-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.0 + - @backstage/plugin-tech-insights-node@0.4.2-next.0 + - @backstage/plugin-techdocs-backend@1.6.1-next.0 + - @backstage/plugin-todo-backend@0.1.41-next.0 + +## 0.2.81 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.12.0 + - @backstage/plugin-catalog-backend@1.8.0 + - @backstage/catalog-client@1.4.0 + - @backstage/plugin-todo-backend@0.1.40 + - @backstage/plugin-permission-node@0.7.6 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.4 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27 + - @backstage/plugin-auth-node@0.2.12 + - @backstage/plugin-techdocs-backend@1.6.0 + - @backstage/backend-tasks@0.5.0 + - @backstage/plugin-tech-insights-backend@0.5.9 + - @backstage/plugin-adr-backend@0.3.1 + - @backstage/plugin-auth-backend@0.18.1 + - @backstage/backend-common@0.18.3 + - @backstage/plugin-linguist-backend@0.2.0 + - @backstage/plugin-catalog-node@1.3.4 + - @backstage/catalog-model@1.2.1 + - @backstage/plugin-events-backend@0.2.4 + - @backstage/plugin-app-backend@0.3.43 + - @backstage/plugin-events-node@0.2.4 + - @backstage/integration@1.4.3 + - @backstage/plugin-azure-devops-backend@0.3.22 + - @backstage/plugin-azure-sites-backend@0.1.5 + - @backstage/plugin-code-coverage-backend@0.2.9 + - @backstage/plugin-entity-feedback-backend@0.1.1 + - @backstage/plugin-explore-backend@0.0.5 + - @backstage/plugin-graphql-backend@0.1.33 + - @backstage/plugin-jenkins-backend@0.1.33 + - @backstage/plugin-kubernetes-backend@0.9.4 + - @backstage/plugin-permission-backend@0.5.18 + - @backstage/plugin-permission-common@0.7.4 + - @backstage/plugin-playlist-backend@0.2.6 + - @backstage/plugin-proxy-backend@0.2.37 + - @backstage/plugin-rollbar-backend@0.1.40 + - @backstage/plugin-lighthouse-backend@0.1.1 + - @backstage/config@1.0.7 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.11 + - @backstage/plugin-badges-backend@0.1.37 + - example-app@0.2.81 + - @backstage/plugin-search-backend@1.2.4 + - @backstage/plugin-kafka-backend@0.2.36 + - @backstage/plugin-search-backend-module-pg@0.5.4 + - @backstage/plugin-search-backend-node@1.1.4 + - @backstage/plugin-search-common@1.2.2 + - @backstage/plugin-tech-insights-node@0.4.1 + +## 0.2.81-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.12.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.4-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27-next.2 + - @backstage/plugin-auth-node@0.2.12-next.2 + - @backstage/backend-tasks@0.5.0-next.2 + - @backstage/plugin-adr-backend@0.3.1-next.2 + - @backstage/backend-common@0.18.3-next.2 + - @backstage/plugin-linguist-backend@0.2.0-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.11-next.2 + - example-app@0.2.81-next.2 + - @backstage/plugin-techdocs-backend@1.5.4-next.2 + - @backstage/plugin-auth-backend@0.18.1-next.2 + - @backstage/plugin-entity-feedback-backend@0.1.1-next.2 + - @backstage/plugin-jenkins-backend@0.1.33-next.2 + - @backstage/plugin-kubernetes-backend@0.9.4-next.2 + - @backstage/plugin-permission-backend@0.5.18-next.2 + - @backstage/plugin-permission-node@0.7.6-next.2 + - @backstage/plugin-playlist-backend@0.2.6-next.2 + - @backstage/plugin-search-backend@1.2.4-next.2 + - @backstage/plugin-lighthouse-backend@0.1.1-next.2 + - @backstage/plugin-search-backend-node@1.1.4-next.2 + - @backstage/plugin-tech-insights-backend@0.5.9-next.2 + - @backstage/plugin-tech-insights-node@0.4.1-next.2 + - @backstage/plugin-app-backend@0.3.43-next.2 + - @backstage/plugin-azure-devops-backend@0.3.22-next.2 + - @backstage/plugin-azure-sites-backend@0.1.5-next.2 + - @backstage/plugin-badges-backend@0.1.37-next.2 + - @backstage/plugin-catalog-backend@1.8.0-next.2 + - @backstage/plugin-catalog-node@1.3.4-next.2 + - @backstage/plugin-code-coverage-backend@0.2.9-next.2 + - @backstage/plugin-events-backend@0.2.4-next.2 + - @backstage/plugin-explore-backend@0.0.5-next.2 + - @backstage/plugin-graphql-backend@0.1.33-next.2 + - @backstage/plugin-kafka-backend@0.2.36-next.2 + - @backstage/plugin-proxy-backend@0.2.37-next.2 + - @backstage/plugin-rollbar-backend@0.1.40-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.4-next.2 + - @backstage/plugin-todo-backend@0.1.40-next.2 + - @backstage/plugin-events-node@0.2.4-next.2 + - @backstage/config@1.0.7-next.0 + - @backstage/integration@1.4.3-next.0 + +## 0.2.81-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.12.0-next.1 + - @backstage/plugin-permission-node@0.7.6-next.1 + - @backstage/plugin-techdocs-backend@1.5.4-next.1 + - @backstage/plugin-auth-backend@0.18.1-next.1 + - @backstage/backend-common@0.18.3-next.1 + - @backstage/catalog-client@1.4.0-next.1 + - @backstage/integration@1.4.3-next.0 + - @backstage/plugin-adr-backend@0.3.1-next.1 + - @backstage/plugin-app-backend@0.3.43-next.1 + - @backstage/plugin-auth-node@0.2.12-next.1 + - @backstage/plugin-azure-devops-backend@0.3.22-next.1 + - @backstage/plugin-azure-sites-backend@0.1.5-next.1 + - @backstage/plugin-catalog-backend@1.8.0-next.1 + - @backstage/plugin-code-coverage-backend@0.2.9-next.1 + - @backstage/plugin-entity-feedback-backend@0.1.1-next.1 + - @backstage/plugin-explore-backend@0.0.5-next.1 + - @backstage/plugin-graphql-backend@0.1.33-next.1 + - @backstage/plugin-jenkins-backend@0.1.33-next.1 + - @backstage/plugin-kubernetes-backend@0.9.4-next.1 + - @backstage/plugin-linguist-backend@0.2.0-next.1 + - @backstage/plugin-permission-backend@0.5.18-next.1 + - @backstage/plugin-permission-common@0.7.4-next.0 + - @backstage/plugin-playlist-backend@0.2.6-next.1 + - @backstage/plugin-proxy-backend@0.2.37-next.1 + - @backstage/plugin-rollbar-backend@0.1.40-next.1 + - @backstage/plugin-todo-backend@0.1.40-next.1 + - @backstage/plugin-lighthouse-backend@0.1.1-next.1 + - @backstage/backend-tasks@0.4.4-next.1 + - @backstage/config@1.0.7-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.11-next.1 + - @backstage/plugin-search-backend@1.2.4-next.1 + - example-app@0.2.81-next.1 + - @backstage/catalog-model@1.2.1-next.1 + - @backstage/plugin-badges-backend@0.1.37-next.1 + - @backstage/plugin-catalog-node@1.3.4-next.1 + - @backstage/plugin-events-backend@0.2.4-next.1 + - @backstage/plugin-events-node@0.2.4-next.1 + - @backstage/plugin-kafka-backend@0.2.36-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.4-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.4-next.1 + - @backstage/plugin-search-backend-node@1.1.4-next.1 + - @backstage/plugin-search-common@1.2.2-next.0 + - @backstage/plugin-tech-insights-backend@0.5.9-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27-next.1 + - @backstage/plugin-tech-insights-node@0.4.1-next.1 + +## 0.2.81-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.4.0-next.0 + - @backstage/plugin-todo-backend@0.1.40-next.0 + - @backstage/plugin-scaffolder-backend@1.11.1-next.0 + - @backstage/plugin-catalog-backend@1.8.0-next.0 + - @backstage/plugin-tech-insights-backend@0.5.9-next.0 + - @backstage/plugin-linguist-backend@0.2.0-next.0 + - @backstage/plugin-adr-backend@0.3.1-next.0 + - @backstage/backend-tasks@0.4.4-next.0 + - @backstage/plugin-techdocs-backend@1.5.4-next.0 + - @backstage/backend-common@0.18.3-next.0 + - @backstage/catalog-model@1.2.1-next.0 + - @backstage/plugin-events-backend@0.2.4-next.0 + - @backstage/plugin-catalog-node@1.3.4-next.0 + - @backstage/plugin-app-backend@0.3.43-next.0 + - @backstage/plugin-events-node@0.2.4-next.0 + - @backstage/plugin-proxy-backend@0.2.37-next.0 + - @backstage/plugin-auth-backend@0.18.1-next.0 + - @backstage/plugin-badges-backend@0.1.37-next.0 + - @backstage/plugin-code-coverage-backend@0.2.9-next.0 + - @backstage/plugin-entity-feedback-backend@0.1.1-next.0 + - @backstage/plugin-jenkins-backend@0.1.33-next.0 + - @backstage/plugin-kubernetes-backend@0.9.4-next.0 + - @backstage/plugin-lighthouse-backend@0.1.1-next.0 + - @backstage/plugin-playlist-backend@0.2.6-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.11-next.0 + - example-app@0.2.81-next.0 + - @backstage/config@1.0.6 + - @backstage/integration@1.4.2 + - @backstage/plugin-auth-node@0.2.12-next.0 + - @backstage/plugin-azure-devops-backend@0.3.22-next.0 + - @backstage/plugin-azure-sites-backend@0.1.5-next.0 + - @backstage/plugin-explore-backend@0.0.5-next.0 + - @backstage/plugin-graphql-backend@0.1.33-next.0 + - @backstage/plugin-kafka-backend@0.2.36-next.0 + - @backstage/plugin-permission-backend@0.5.18-next.0 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.6-next.0 + - @backstage/plugin-rollbar-backend@0.1.40-next.0 + - @backstage/plugin-search-backend@1.2.4-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.4-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.4-next.0 + - @backstage/plugin-search-backend-node@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27-next.0 + - @backstage/plugin-tech-insights-node@0.4.1-next.0 + +## 0.2.80 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.7.2 + - @backstage/plugin-playlist-backend@0.2.5 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.3 + - @backstage/backend-common@0.18.2 + - @backstage/plugin-kubernetes-backend@0.9.3 + - @backstage/plugin-lighthouse-backend@0.1.0 + - @backstage/plugin-code-coverage-backend@0.2.8 + - @backstage/plugin-azure-devops-backend@0.3.21 + - @backstage/plugin-azure-sites-backend@0.1.4 + - @backstage/plugin-adr-backend@0.3.0 + - @backstage/plugin-tech-insights-backend@0.5.8 + - @backstage/plugin-tech-insights-node@0.4.0 + - @backstage/plugin-entity-feedback-backend@0.1.0 + - @backstage/plugin-search-backend@1.2.3 + - @backstage/plugin-techdocs-backend@1.5.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.10 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26 + - @backstage/plugin-scaffolder-backend@1.11.0 + - @backstage/plugin-events-backend@0.2.3 + - @backstage/plugin-kafka-backend@0.2.35 + - @backstage/plugin-proxy-backend@0.2.36 + - @backstage/plugin-app-backend@0.3.42 + - @backstage/catalog-model@1.2.0 + - @backstage/plugin-auth-backend@0.18.0 + - @backstage/plugin-linguist-backend@0.1.0 + - @backstage/plugin-events-node@0.2.3 + - example-app@0.2.80 + - @backstage/plugin-catalog-node@1.3.3 + - @backstage/backend-tasks@0.4.3 + - @backstage/catalog-client@1.3.1 + - @backstage/config@1.0.6 + - @backstage/integration@1.4.2 + - @backstage/plugin-auth-node@0.2.11 + - @backstage/plugin-badges-backend@0.1.36 + - @backstage/plugin-explore-backend@0.0.4 + - @backstage/plugin-graphql-backend@0.1.32 + - @backstage/plugin-jenkins-backend@0.1.32 + - @backstage/plugin-permission-backend@0.5.17 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.5 + - @backstage/plugin-rollbar-backend@0.1.39 + - @backstage/plugin-search-backend-module-pg@0.5.3 + - @backstage/plugin-search-backend-node@1.1.3 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-todo-backend@0.1.39 + +## 0.2.80-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-lighthouse-backend@0.1.0-next.0 + - @backstage/backend-common@0.18.2-next.2 + - @backstage/plugin-catalog-backend@1.7.2-next.2 + - @backstage/plugin-search-backend@1.2.3-next.2 + - @backstage/plugin-scaffolder-backend@1.11.0-next.2 + - @backstage/plugin-events-backend@0.2.3-next.2 + - @backstage/plugin-kafka-backend@0.2.35-next.2 + - @backstage/plugin-proxy-backend@0.2.36-next.2 + - @backstage/plugin-app-backend@0.3.42-next.2 + - @backstage/catalog-model@1.2.0-next.1 + - @backstage/plugin-kubernetes-backend@0.9.3-next.2 + - @backstage/plugin-events-node@0.2.3-next.2 + - @backstage/plugin-catalog-node@1.3.3-next.2 + - @backstage/backend-tasks@0.4.3-next.2 + - @backstage/plugin-auth-backend@0.17.5-next.2 + - @backstage/plugin-auth-node@0.2.11-next.2 + - @backstage/plugin-permission-node@0.7.5-next.2 + - @backstage/plugin-playlist-backend@0.2.5-next.2 + - @backstage/plugin-rollbar-backend@0.1.39-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.3-next.2 + - @backstage/plugin-tech-insights-backend@0.5.8-next.2 + - @backstage/plugin-techdocs-backend@1.5.3-next.2 + - example-app@0.2.80-next.2 + - @backstage/catalog-client@1.3.1-next.1 + - @backstage/config@1.0.6 + - @backstage/integration@1.4.2 + - @backstage/plugin-adr-backend@0.2.7-next.2 + - @backstage/plugin-azure-devops-backend@0.3.21-next.2 + - @backstage/plugin-azure-sites-backend@0.1.4-next.2 + - @backstage/plugin-badges-backend@0.1.36-next.2 + - @backstage/plugin-code-coverage-backend@0.2.8-next.2 + - @backstage/plugin-explore-backend@0.0.4-next.2 + - @backstage/plugin-graphql-backend@0.1.32-next.2 + - @backstage/plugin-jenkins-backend@0.1.32-next.2 + - @backstage/plugin-permission-backend@0.5.17-next.2 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.10-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.3-next.2 + - @backstage/plugin-search-backend-node@1.1.3-next.2 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26-next.2 + - @backstage/plugin-tech-insights-node@0.4.0-next.2 + - @backstage/plugin-todo-backend@0.1.39-next.2 + +## 0.2.80-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.7.2-next.1 + - @backstage/plugin-tech-insights-backend@0.5.8-next.1 + - @backstage/plugin-tech-insights-node@0.4.0-next.1 + - @backstage/plugin-azure-devops-backend@0.3.21-next.1 + - @backstage/backend-common@0.18.2-next.1 + - @backstage/plugin-kubernetes-backend@0.9.3-next.1 + - @backstage/plugin-scaffolder-backend@1.11.0-next.1 + - @backstage/plugin-playlist-backend@0.2.5-next.1 + - example-app@0.2.80-next.1 + - @backstage/backend-tasks@0.4.3-next.1 + - @backstage/catalog-client@1.3.1-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/config@1.0.6 + - @backstage/integration@1.4.2 + - @backstage/plugin-adr-backend@0.2.7-next.1 + - @backstage/plugin-app-backend@0.3.42-next.1 + - @backstage/plugin-auth-backend@0.17.5-next.1 + - @backstage/plugin-auth-node@0.2.11-next.1 + - @backstage/plugin-azure-sites-backend@0.1.4-next.1 + - @backstage/plugin-badges-backend@0.1.36-next.1 + - @backstage/plugin-catalog-node@1.3.3-next.1 + - @backstage/plugin-code-coverage-backend@0.2.8-next.1 + - @backstage/plugin-events-backend@0.2.3-next.1 + - @backstage/plugin-events-node@0.2.3-next.1 + - @backstage/plugin-explore-backend@0.0.4-next.1 + - @backstage/plugin-graphql-backend@0.1.32-next.1 + - @backstage/plugin-jenkins-backend@0.1.32-next.1 + - @backstage/plugin-kafka-backend@0.2.35-next.1 + - @backstage/plugin-permission-backend@0.5.17-next.1 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.5-next.1 + - @backstage/plugin-proxy-backend@0.2.36-next.1 + - @backstage/plugin-rollbar-backend@0.1.39-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.10-next.1 + - @backstage/plugin-search-backend@1.2.3-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.3-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.3-next.1 + - @backstage/plugin-search-backend-node@1.1.3-next.1 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26-next.1 + - @backstage/plugin-techdocs-backend@1.5.3-next.1 + - @backstage/plugin-todo-backend@0.1.39-next.1 + +## 0.2.80-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.9.3-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.10-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26-next.0 + - @backstage/plugin-scaffolder-backend@1.11.0-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - example-app@0.2.80-next.0 + - @backstage/plugin-techdocs-backend@1.5.3-next.0 + - @backstage/backend-common@0.18.2-next.0 + - @backstage/plugin-app-backend@0.3.42-next.0 + - @backstage/catalog-client@1.3.1-next.0 + - @backstage/plugin-adr-backend@0.2.7-next.0 + - @backstage/plugin-auth-backend@0.17.5-next.0 + - @backstage/plugin-badges-backend@0.1.36-next.0 + - @backstage/plugin-catalog-backend@1.7.2-next.0 + - @backstage/plugin-catalog-node@1.3.3-next.0 + - @backstage/plugin-code-coverage-backend@0.2.8-next.0 + - @backstage/plugin-jenkins-backend@0.1.32-next.0 + - @backstage/plugin-kafka-backend@0.2.35-next.0 + - @backstage/plugin-playlist-backend@0.2.5-next.0 + - @backstage/plugin-tech-insights-backend@0.5.8-next.0 + - @backstage/plugin-todo-backend@0.1.39-next.0 + - @backstage/backend-tasks@0.4.3-next.0 + - @backstage/plugin-auth-node@0.2.11-next.0 + - @backstage/plugin-events-backend@0.2.3-next.0 + - @backstage/plugin-permission-node@0.7.5-next.0 + - @backstage/plugin-rollbar-backend@0.1.39-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.3-next.0 + - @backstage/plugin-azure-devops-backend@0.3.21-next.0 + - @backstage/plugin-azure-sites-backend@0.1.4-next.0 + - @backstage/plugin-explore-backend@0.0.4-next.0 + - @backstage/plugin-graphql-backend@0.1.32-next.0 + - @backstage/plugin-permission-backend@0.5.17-next.0 + - @backstage/plugin-proxy-backend@0.2.36-next.0 + - @backstage/plugin-search-backend@1.2.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.3-next.0 + - @backstage/plugin-search-backend-node@1.1.3-next.0 + - @backstage/plugin-tech-insights-node@0.3.10-next.0 + - @backstage/plugin-events-node@0.2.3-next.0 + +## 0.2.79 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.10.0 + - @backstage/backend-common@0.18.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.8 + - @backstage/plugin-adr-backend@0.2.5 + - @backstage/catalog-model@1.1.5 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.1 + - @backstage/plugin-search-backend-node@1.1.1 + - @backstage/catalog-client@1.3.0 + - @backstage/plugin-explore-backend@0.0.2 + - @backstage/backend-tasks@0.4.1 + - @backstage/plugin-events-backend@0.2.1 + - @backstage/plugin-catalog-node@1.3.1 + - @backstage/plugin-app-backend@0.3.40 + - @backstage/plugin-code-coverage-backend@0.2.6 + - @backstage/plugin-catalog-backend@1.7.0 + - @backstage/plugin-tech-insights-backend@0.5.6 + - @backstage/plugin-kubernetes-backend@0.9.1 + - @backstage/config@1.0.6 + - @backstage/plugin-search-backend@1.2.1 + - @backstage/plugin-events-node@0.2.1 + - example-app@0.2.79 + - @backstage/integration@1.4.2 + - @backstage/plugin-auth-backend@0.17.3 + - @backstage/plugin-auth-node@0.2.9 + - @backstage/plugin-azure-devops-backend@0.3.19 + - @backstage/plugin-azure-sites-backend@0.1.2 + - @backstage/plugin-badges-backend@0.1.34 + - @backstage/plugin-graphql-backend@0.1.30 + - @backstage/plugin-jenkins-backend@0.1.30 + - @backstage/plugin-kafka-backend@0.2.33 + - @backstage/plugin-permission-backend@0.5.15 + - @backstage/plugin-permission-common@0.7.3 + - @backstage/plugin-permission-node@0.7.3 + - @backstage/plugin-playlist-backend@0.2.3 + - @backstage/plugin-proxy-backend@0.2.34 + - @backstage/plugin-rollbar-backend@0.1.37 + - @backstage/plugin-search-backend-module-pg@0.5.1 + - @backstage/plugin-search-common@1.2.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.24 + - @backstage/plugin-tech-insights-node@0.3.8 + - @backstage/plugin-techdocs-backend@1.5.1 + - @backstage/plugin-todo-backend@0.1.37 + +## 0.2.79-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-adr-backend@0.2.5-next.2 + - @backstage/backend-common@0.18.0-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.8-next.2 + - @backstage/plugin-scaffolder-backend@1.10.0-next.2 + - @backstage/backend-tasks@0.4.1-next.1 + - @backstage/catalog-client@1.3.0-next.2 + - @backstage/plugin-catalog-backend@1.7.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.1-next.2 + - @backstage/plugin-events-backend@0.2.1-next.1 + - @backstage/plugin-app-backend@0.3.40-next.1 + - @backstage/plugin-kubernetes-backend@0.9.1-next.2 + - @backstage/plugin-catalog-node@1.3.1-next.2 + - @backstage/plugin-events-node@0.2.1-next.1 + - @backstage/plugin-auth-backend@0.17.3-next.2 + - @backstage/plugin-auth-node@0.2.9-next.1 + - @backstage/plugin-permission-node@0.7.3-next.1 + - @backstage/plugin-playlist-backend@0.2.3-next.2 + - @backstage/plugin-rollbar-backend@0.1.37-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.1-next.2 + - @backstage/plugin-tech-insights-backend@0.5.6-next.2 + - @backstage/plugin-techdocs-backend@1.5.1-next.2 + - example-app@0.2.79-next.2 + - @backstage/plugin-azure-devops-backend@0.3.19-next.1 + - @backstage/plugin-azure-sites-backend@0.1.2-next.1 + - @backstage/plugin-badges-backend@0.1.34-next.2 + - @backstage/plugin-code-coverage-backend@0.2.6-next.2 + - @backstage/plugin-explore-backend@0.0.2-next.2 + - @backstage/plugin-graphql-backend@0.1.30-next.2 + - @backstage/plugin-jenkins-backend@0.1.30-next.2 + - @backstage/plugin-kafka-backend@0.2.33-next.2 + - @backstage/plugin-permission-backend@0.5.15-next.1 + - @backstage/plugin-proxy-backend@0.2.34-next.1 + - @backstage/plugin-search-backend@1.2.1-next.2 + - @backstage/plugin-search-backend-node@1.1.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.24-next.1 + - @backstage/plugin-tech-insights-node@0.3.8-next.1 + - @backstage/plugin-todo-backend@0.1.37-next.2 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/integration@1.4.2-next.0 + - @backstage/plugin-permission-common@0.7.3-next.0 + - @backstage/plugin-search-common@1.2.1-next.0 + +## 0.2.79-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.10.0-next.1 + - @backstage/backend-common@0.18.0-next.0 + - @backstage/plugin-explore-backend@0.0.2-next.1 + - @backstage/plugin-events-backend@0.2.1-next.0 + - @backstage/plugin-app-backend@0.3.40-next.0 + - @backstage/plugin-tech-insights-backend@0.5.6-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/plugin-catalog-backend@1.7.0-next.1 + - @backstage/plugin-catalog-node@1.3.1-next.1 + - @backstage/plugin-events-node@0.2.1-next.0 + - example-app@0.2.79-next.1 + - @backstage/backend-tasks@0.4.1-next.0 + - @backstage/catalog-client@1.3.0-next.1 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/integration@1.4.2-next.0 + - @backstage/plugin-auth-backend@0.17.3-next.1 + - @backstage/plugin-auth-node@0.2.9-next.0 + - @backstage/plugin-azure-devops-backend@0.3.19-next.0 + - @backstage/plugin-azure-sites-backend@0.1.2-next.0 + - @backstage/plugin-badges-backend@0.1.34-next.1 + - @backstage/plugin-code-coverage-backend@0.2.6-next.1 + - @backstage/plugin-graphql-backend@0.1.30-next.1 + - @backstage/plugin-jenkins-backend@0.1.30-next.1 + - @backstage/plugin-kafka-backend@0.2.33-next.1 + - @backstage/plugin-kubernetes-backend@0.9.1-next.1 + - @backstage/plugin-permission-backend@0.5.15-next.0 + - @backstage/plugin-permission-common@0.7.3-next.0 + - @backstage/plugin-permission-node@0.7.3-next.0 + - @backstage/plugin-playlist-backend@0.2.3-next.1 + - @backstage/plugin-proxy-backend@0.2.34-next.0 + - @backstage/plugin-rollbar-backend@0.1.37-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.8-next.1 + - @backstage/plugin-search-backend@1.2.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.1-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.1-next.1 + - @backstage/plugin-search-backend-node@1.1.1-next.1 + - @backstage/plugin-search-common@1.2.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.24-next.0 + - @backstage/plugin-tech-insights-node@0.3.8-next.0 + - @backstage/plugin-techdocs-backend@1.5.1-next.1 + - @backstage/plugin-todo-backend@0.1.37-next.1 + +## 0.2.79-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.5-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.1-next.0 + - @backstage/plugin-search-backend-node@1.1.1-next.0 + - @backstage/catalog-client@1.3.0-next.0 + - @backstage/plugin-explore-backend@0.0.2-next.0 + - @backstage/plugin-code-coverage-backend@0.2.6-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.8-next.0 + - @backstage/plugin-scaffolder-backend@1.9.1-next.0 + - @backstage/plugin-catalog-backend@1.7.0-next.0 + - @backstage/plugin-search-backend@1.2.1-next.0 + - example-app@0.2.79-next.0 + - @backstage/backend-common@0.17.0 + - @backstage/backend-tasks@0.4.0 + - @backstage/config@1.0.5 + - @backstage/integration@1.4.1 + - @backstage/plugin-app-backend@0.3.39 + - @backstage/plugin-auth-backend@0.17.3-next.0 + - @backstage/plugin-auth-node@0.2.8 + - @backstage/plugin-azure-devops-backend@0.3.18 + - @backstage/plugin-azure-sites-backend@0.1.1 + - @backstage/plugin-badges-backend@0.1.34-next.0 + - @backstage/plugin-catalog-node@1.3.1-next.0 + - @backstage/plugin-events-backend@0.2.0 + - @backstage/plugin-events-node@0.2.0 + - @backstage/plugin-graphql-backend@0.1.30-next.0 + - @backstage/plugin-jenkins-backend@0.1.30-next.0 + - @backstage/plugin-kafka-backend@0.2.33-next.0 + - @backstage/plugin-kubernetes-backend@0.9.1-next.0 + - @backstage/plugin-permission-backend@0.5.14 + - @backstage/plugin-permission-common@0.7.2 + - @backstage/plugin-permission-node@0.7.2 + - @backstage/plugin-playlist-backend@0.2.3-next.0 + - @backstage/plugin-proxy-backend@0.2.33 + - @backstage/plugin-rollbar-backend@0.1.36 + - @backstage/plugin-search-backend-module-pg@0.5.1-next.0 + - @backstage/plugin-search-common@1.2.0 + - @backstage/plugin-tech-insights-backend@0.5.6-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23 + - @backstage/plugin-tech-insights-node@0.3.7 + - @backstage/plugin-techdocs-backend@1.5.1-next.0 + - @backstage/plugin-todo-backend@0.1.37-next.0 + +## 0.2.78 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.9.0 + - @backstage/plugin-azure-devops-backend@0.3.18 + - @backstage/plugin-kubernetes-backend@0.9.0 + - @backstage/plugin-catalog-backend@1.6.0 + - @backstage/catalog-client@1.2.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.7 + - @backstage/plugin-search-backend@1.2.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.0 + - @backstage/plugin-search-backend-node@1.1.0 + - @backstage/plugin-playlist-backend@0.2.2 + - @backstage/plugin-search-backend-module-pg@0.5.0 + - @backstage/backend-common@0.17.0 + - @backstage/plugin-app-backend@0.3.39 + - @backstage/plugin-catalog-node@1.3.0 + - @backstage/plugin-events-backend@0.2.0 + - @backstage/backend-tasks@0.4.0 + - @backstage/plugin-permission-backend@0.5.14 + - @backstage/plugin-permission-common@0.7.2 + - @backstage/plugin-permission-node@0.7.2 + - @backstage/plugin-kafka-backend@0.2.32 + - @backstage/plugin-jenkins-backend@0.1.29 + - @backstage/plugin-events-node@0.2.0 + - @backstage/integration@1.4.1 + - @backstage/plugin-auth-backend@0.17.2 + - @backstage/plugin-auth-node@0.2.8 + - @backstage/plugin-azure-sites-backend@0.1.1 + - @backstage/plugin-code-coverage-backend@0.2.5 + - @backstage/plugin-graphql-backend@0.1.29 + - @backstage/plugin-proxy-backend@0.2.33 + - @backstage/plugin-rollbar-backend@0.1.36 + - @backstage/plugin-techdocs-backend@1.5.0 + - @backstage/plugin-todo-backend@0.1.36 + - @backstage/plugin-explore-backend@0.0.1 + - @backstage/plugin-search-common@1.2.0 + - example-app@0.2.78 + - @backstage/plugin-badges-backend@0.1.33 + - @backstage/plugin-tech-insights-backend@0.5.5 + - @backstage/catalog-model@1.1.4 + - @backstage/config@1.0.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23 + - @backstage/plugin-tech-insights-node@0.3.7 + +## 0.2.78-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.6.0-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.3 + - @backstage/plugin-scaffolder-backend@1.9.0-next.3 + - @backstage/backend-tasks@0.4.0-next.3 + - @backstage/plugin-permission-backend@0.5.14-next.3 + - @backstage/plugin-permission-common@0.7.2-next.2 + - @backstage/plugin-permission-node@0.7.2-next.3 + - @backstage/plugin-playlist-backend@0.2.2-next.4 + - @backstage/plugin-search-backend@1.2.0-next.3 + - @backstage/plugin-kubernetes-backend@0.8.1-next.4 + - @backstage/backend-common@0.17.0-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.0-next.3 + - @backstage/plugin-techdocs-backend@1.5.0-next.3 + - example-app@0.2.78-next.4 + - @backstage/catalog-client@1.2.0-next.1 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/plugin-app-backend@0.3.39-next.3 + - @backstage/plugin-auth-backend@0.17.2-next.3 + - @backstage/plugin-auth-node@0.2.8-next.3 + - @backstage/plugin-azure-devops-backend@0.3.18-next.3 + - @backstage/plugin-azure-sites-backend@0.1.1-next.3 + - @backstage/plugin-badges-backend@0.1.33-next.3 + - @backstage/plugin-catalog-node@1.3.0-next.3 + - @backstage/plugin-code-coverage-backend@0.2.5-next.3 + - @backstage/plugin-events-backend@0.2.0-next.3 + - @backstage/plugin-events-node@0.2.0-next.3 + - @backstage/plugin-explore-backend@0.0.1-next.2 + - @backstage/plugin-graphql-backend@0.1.29-next.3 + - @backstage/plugin-jenkins-backend@0.1.29-next.3 + - @backstage/plugin-kafka-backend@0.2.32-next.3 + - @backstage/plugin-proxy-backend@0.2.33-next.3 + - @backstage/plugin-rollbar-backend@0.1.36-next.3 + - @backstage/plugin-search-backend-module-pg@0.4.3-next.3 + - @backstage/plugin-search-backend-node@1.1.0-next.3 + - @backstage/plugin-search-common@1.2.0-next.3 + - @backstage/plugin-tech-insights-backend@0.5.5-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.3 + - @backstage/plugin-tech-insights-node@0.3.7-next.3 + - @backstage/plugin-todo-backend@0.1.36-next.3 + +## 0.2.78-next.3 + +### Patch Changes + +- Updated dependencies + - example-app@0.2.78-next.3 + - @backstage/backend-common@0.17.0-next.2 + - @backstage/backend-tasks@0.4.0-next.2 + - @backstage/catalog-client@1.2.0-next.1 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/plugin-app-backend@0.3.39-next.2 + - @backstage/plugin-auth-backend@0.17.2-next.2 + - @backstage/plugin-auth-node@0.2.8-next.2 + - @backstage/plugin-azure-devops-backend@0.3.18-next.2 + - @backstage/plugin-azure-sites-backend@0.1.1-next.2 + - @backstage/plugin-badges-backend@0.1.33-next.2 + - @backstage/plugin-catalog-backend@1.6.0-next.2 + - @backstage/plugin-catalog-node@1.3.0-next.2 + - @backstage/plugin-code-coverage-backend@0.2.5-next.2 + - @backstage/plugin-events-backend@0.2.0-next.2 + - @backstage/plugin-events-node@0.2.0-next.2 + - @backstage/plugin-explore-backend@0.0.1-next.1 + - @backstage/plugin-graphql-backend@0.1.29-next.2 + - @backstage/plugin-jenkins-backend@0.1.29-next.2 + - @backstage/plugin-kafka-backend@0.2.32-next.2 + - @backstage/plugin-kubernetes-backend@0.8.1-next.3 + - @backstage/plugin-permission-backend@0.5.14-next.2 + - @backstage/plugin-permission-common@0.7.2-next.1 + - @backstage/plugin-permission-node@0.7.2-next.2 + - @backstage/plugin-playlist-backend@0.2.2-next.3 + - @backstage/plugin-proxy-backend@0.2.33-next.2 + - @backstage/plugin-rollbar-backend@0.1.36-next.2 + - @backstage/plugin-scaffolder-backend@1.9.0-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.2 + - @backstage/plugin-search-backend@1.2.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.0-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.3-next.2 + - @backstage/plugin-search-backend-node@1.1.0-next.2 + - @backstage/plugin-search-common@1.2.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.5-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.2 + - @backstage/plugin-tech-insights-node@0.3.7-next.2 + - @backstage/plugin-techdocs-backend@1.4.2-next.2 + - @backstage/plugin-todo-backend@0.1.36-next.2 + +## 0.2.78-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.3.18-next.2 + - @backstage/plugin-search-backend@1.2.0-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.1.0-next.2 + - @backstage/plugin-search-backend-node@1.1.0-next.2 + - @backstage/plugin-catalog-backend@1.6.0-next.2 + - @backstage/plugin-playlist-backend@0.2.2-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.3-next.2 + - @backstage/plugin-app-backend@0.3.39-next.2 + - @backstage/plugin-catalog-node@1.3.0-next.2 + - @backstage/plugin-events-backend@0.2.0-next.2 + - @backstage/plugin-scaffolder-backend@1.9.0-next.2 + - @backstage/backend-common@0.17.0-next.2 + - @backstage/plugin-search-common@1.2.0-next.2 + - example-app@0.2.78-next.2 + - @backstage/plugin-techdocs-backend@1.4.2-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.2 + - @backstage/backend-tasks@0.4.0-next.2 + - @backstage/plugin-auth-backend@0.17.2-next.2 + - @backstage/plugin-auth-node@0.2.8-next.2 + - @backstage/plugin-azure-sites-backend@0.1.1-next.2 + - @backstage/plugin-badges-backend@0.1.33-next.2 + - @backstage/plugin-code-coverage-backend@0.2.5-next.2 + - @backstage/plugin-explore-backend@0.0.1-next.1 + - @backstage/plugin-graphql-backend@0.1.29-next.2 + - @backstage/plugin-jenkins-backend@0.1.29-next.2 + - @backstage/plugin-kafka-backend@0.2.32-next.2 + - @backstage/plugin-kubernetes-backend@0.8.1-next.2 + - @backstage/plugin-permission-backend@0.5.14-next.2 + - @backstage/plugin-permission-node@0.7.2-next.2 + - @backstage/plugin-proxy-backend@0.2.33-next.2 + - @backstage/plugin-rollbar-backend@0.1.36-next.2 + - @backstage/plugin-tech-insights-backend@0.5.5-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.2 + - @backstage/plugin-tech-insights-node@0.3.7-next.2 + - @backstage/plugin-todo-backend@0.1.36-next.2 + - @backstage/catalog-client@1.2.0-next.1 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/plugin-events-node@0.2.0-next.2 + - @backstage/plugin-permission-common@0.7.2-next.1 + +## 0.2.78-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/plugin-catalog-backend@1.6.0-next.1 + - @backstage/plugin-kafka-backend@0.2.32-next.1 + - @backstage/backend-tasks@0.4.0-next.1 + - @backstage/plugin-search-backend-node@1.0.5-next.1 + - @backstage/plugin-jenkins-backend@0.1.29-next.1 + - @backstage/plugin-scaffolder-backend@1.8.1-next.1 + - @backstage/plugin-explore-backend@0.0.1-next.0 + - @backstage/plugin-proxy-backend@0.2.33-next.1 + - @backstage/plugin-app-backend@0.3.39-next.1 + - @backstage/plugin-auth-backend@0.17.2-next.1 + - @backstage/plugin-auth-node@0.2.8-next.1 + - @backstage/plugin-azure-devops-backend@0.3.18-next.1 + - @backstage/plugin-azure-sites-backend@0.1.1-next.1 + - @backstage/plugin-badges-backend@0.1.33-next.1 + - @backstage/plugin-catalog-node@1.2.2-next.1 + - @backstage/plugin-code-coverage-backend@0.2.5-next.1 + - @backstage/plugin-events-backend@0.2.0-next.1 + - @backstage/plugin-graphql-backend@0.1.29-next.1 + - @backstage/plugin-kubernetes-backend@0.8.1-next.1 + - @backstage/plugin-permission-backend@0.5.14-next.1 + - @backstage/plugin-permission-node@0.7.2-next.1 + - @backstage/plugin-playlist-backend@0.2.2-next.1 + - @backstage/plugin-rollbar-backend@0.1.36-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.1 + - @backstage/plugin-search-backend@1.1.2-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.5-next.1 + - @backstage/plugin-search-backend-module-pg@0.4.3-next.1 + - @backstage/plugin-tech-insights-backend@0.5.5-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.1 + - @backstage/plugin-tech-insights-node@0.3.7-next.1 + - @backstage/plugin-techdocs-backend@1.4.2-next.1 + - @backstage/plugin-todo-backend@0.1.36-next.1 + - example-app@0.2.78-next.1 + - @backstage/config@1.0.5-next.1 + - @backstage/integration@1.4.1-next.1 + - @backstage/catalog-client@1.2.0-next.1 + - @backstage/catalog-model@1.1.4-next.1 + - @backstage/plugin-events-node@0.2.0-next.1 + - @backstage/plugin-permission-common@0.7.2-next.1 + - @backstage/plugin-search-common@1.1.2-next.1 + +## 0.2.78-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.1-next.0 + - @backstage/catalog-client@1.2.0-next.0 + - @backstage/plugin-catalog-backend@1.6.0-next.0 + - @backstage/plugin-events-backend@0.2.0-next.0 + - @backstage/plugin-search-backend-node@1.0.5-next.0 + - @backstage/plugin-events-node@0.2.0-next.0 + - @backstage/backend-common@0.16.1-next.0 + - @backstage/integration@1.4.1-next.0 + - @backstage/plugin-app-backend@0.3.39-next.0 + - @backstage/plugin-auth-backend@0.17.2-next.0 + - @backstage/plugin-auth-node@0.2.8-next.0 + - @backstage/plugin-azure-devops-backend@0.3.18-next.0 + - @backstage/plugin-azure-sites-backend@0.1.1-next.0 + - @backstage/plugin-code-coverage-backend@0.2.5-next.0 + - @backstage/plugin-graphql-backend@0.1.29-next.0 + - @backstage/plugin-jenkins-backend@0.1.29-next.0 + - @backstage/plugin-permission-backend@0.5.14-next.0 + - @backstage/plugin-permission-common@0.7.2-next.0 + - @backstage/plugin-permission-node@0.7.2-next.0 + - @backstage/plugin-playlist-backend@0.2.2-next.0 + - @backstage/plugin-proxy-backend@0.2.33-next.0 + - @backstage/plugin-rollbar-backend@0.1.36-next.0 + - @backstage/plugin-techdocs-backend@1.4.2-next.0 + - @backstage/plugin-todo-backend@0.1.36-next.0 + - @backstage/plugin-kubernetes-backend@0.8.1-next.0 + - example-app@0.2.78-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.0 + - @backstage/plugin-badges-backend@0.1.33-next.0 + - @backstage/plugin-catalog-node@1.2.2-next.0 + - @backstage/plugin-tech-insights-backend@0.5.5-next.0 + - @backstage/backend-tasks@0.3.8-next.0 + - @backstage/catalog-model@1.1.4-next.0 + - @backstage/config@1.0.5-next.0 + - @backstage/plugin-kafka-backend@0.2.32-next.0 + - @backstage/plugin-search-backend@1.1.2-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.5-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.3-next.0 + - @backstage/plugin-search-common@1.1.2-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.0 + - @backstage/plugin-tech-insights-node@0.3.7-next.0 + +## 0.2.77 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/plugin-auth-backend@0.17.1 + - @backstage/plugin-catalog-backend@1.5.1 + - @backstage/plugin-techdocs-backend@1.4.1 + - @backstage/plugin-scaffolder-backend@1.8.0 + - @backstage/integration@1.4.0 + - @backstage/backend-tasks@0.3.7 + - @backstage/plugin-playlist-backend@0.2.1 + - @backstage/plugin-azure-devops-backend@0.3.17 + - @backstage/catalog-model@1.1.3 + - @backstage/plugin-auth-node@0.2.7 + - @backstage/plugin-permission-common@0.7.1 + - @backstage/plugin-code-coverage-backend@0.2.4 + - @backstage/plugin-events-backend@0.1.0 + - @backstage/plugin-events-node@0.1.0 + - @backstage/plugin-kubernetes-backend@0.8.0 + - @backstage/plugin-tech-insights-backend@0.5.4 + - @backstage/plugin-tech-insights-node@0.3.6 + - @backstage/plugin-azure-sites-backend@0.1.0 + - example-app@0.2.77 + - @backstage/plugin-app-backend@0.3.38 + - @backstage/plugin-badges-backend@0.1.32 + - @backstage/plugin-catalog-node@1.2.1 + - @backstage/plugin-graphql-backend@0.1.28 + - @backstage/plugin-jenkins-backend@0.1.28 + - @backstage/plugin-kafka-backend@0.2.31 + - @backstage/plugin-permission-backend@0.5.13 + - @backstage/plugin-permission-node@0.7.1 + - @backstage/plugin-proxy-backend@0.2.32 + - @backstage/plugin-rollbar-backend@0.1.35 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6 + - @backstage/plugin-search-backend@1.1.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.4 + - @backstage/plugin-search-backend-module-pg@0.4.2 + - @backstage/plugin-search-backend-node@1.0.4 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22 + - @backstage/plugin-todo-backend@0.1.35 + - @backstage/catalog-client@1.1.2 + - @backstage/config@1.0.4 + - @backstage/plugin-search-common@1.1.1 + +## 0.2.77-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.17.1-next.1 + - @backstage/backend-common@0.16.0-next.1 + - @backstage/plugin-scaffolder-backend@1.8.0-next.2 + - @backstage/plugin-code-coverage-backend@0.2.4-next.1 + - @backstage/plugin-kubernetes-backend@0.8.0-next.1 + - @backstage/plugin-tech-insights-backend@0.5.4-next.1 + - example-app@0.2.77-next.2 + - @backstage/backend-tasks@0.3.7-next.1 + - @backstage/plugin-app-backend@0.3.38-next.1 + - @backstage/plugin-auth-node@0.2.7-next.1 + - @backstage/plugin-azure-devops-backend@0.3.17-next.2 + - @backstage/plugin-azure-sites-backend@0.1.0-next.1 + - @backstage/plugin-badges-backend@0.1.32-next.1 + - @backstage/plugin-catalog-backend@1.5.1-next.1 + - @backstage/plugin-graphql-backend@0.1.28-next.1 + - @backstage/plugin-jenkins-backend@0.1.28-next.1 + - @backstage/plugin-kafka-backend@0.2.31-next.1 + - @backstage/plugin-permission-backend@0.5.13-next.1 + - @backstage/plugin-permission-node@0.7.1-next.1 + - @backstage/plugin-playlist-backend@0.2.1-next.2 + - @backstage/plugin-proxy-backend@0.2.32-next.1 + - @backstage/plugin-rollbar-backend@0.1.35-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.2 + - @backstage/plugin-search-backend@1.1.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.1 + - @backstage/plugin-search-backend-module-pg@0.4.2-next.1 + - @backstage/plugin-search-backend-node@1.0.4-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.1 + - @backstage/plugin-tech-insights-node@0.3.6-next.1 + - @backstage/plugin-techdocs-backend@1.4.1-next.1 + - @backstage/plugin-todo-backend@0.1.35-next.1 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.77-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + - @backstage/plugin-playlist-backend@0.2.1-next.1 + - @backstage/plugin-azure-devops-backend@0.3.17-next.1 + - example-app@0.2.77-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.1 + +## 0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-techdocs-backend@1.4.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.17.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.4-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/plugin-azure-sites-backend@0.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.8.0-next.0 + - example-app@0.2.77-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/plugin-azure-devops-backend@0.3.17-next.0 + - @backstage/plugin-badges-backend@0.1.32-next.0 + - @backstage/plugin-code-coverage-backend@0.2.4-next.0 + - @backstage/plugin-graphql-backend@0.1.28-next.0 + - @backstage/plugin-jenkins-backend@0.1.28-next.0 + - @backstage/plugin-kafka-backend@0.2.31-next.0 + - @backstage/plugin-permission-backend@0.5.13-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-playlist-backend@0.2.1-next.0 + - @backstage/plugin-proxy-backend@0.2.32-next.0 + - @backstage/plugin-rollbar-backend@0.1.35-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 + - @backstage/plugin-search-backend@1.1.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.2-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 + - @backstage/plugin-todo-backend@0.1.35-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-auth-backend@0.17.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-tech-insights-backend@0.5.3 + - @backstage/plugin-search-backend@1.1.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-playlist-backend@0.2.0 + - @backstage/plugin-jenkins-backend@0.1.27 + - @backstage/plugin-app-backend@0.3.37 + - @backstage/plugin-badges-backend@0.1.31 + - @backstage/plugin-graphql-backend@0.1.27 + - @backstage/plugin-permission-backend@0.5.12 + - @backstage/plugin-rollbar-backend@0.1.34 + - @backstage/plugin-kubernetes-backend@0.7.3 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/plugin-search-backend-module-pg@0.4.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3 + - @backstage/plugin-techdocs-backend@1.4.0 + - @backstage/plugin-tech-insights-node@0.3.5 + - example-app@0.2.76 + - @backstage/plugin-code-coverage-backend@0.2.3 + - @backstage/plugin-kafka-backend@0.2.30 + - @backstage/plugin-todo-backend@0.1.34 + - @backstage/plugin-azure-devops-backend@0.3.16 + - @backstage/plugin-proxy-backend@0.2.31 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-playlist-backend@0.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.27-next.2 + - @backstage/plugin-permission-backend@0.5.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.34-next.2 + - @backstage/plugin-search-backend@1.1.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.3-next.2 + - @backstage/plugin-techdocs-backend@1.4.0-next.2 + - example-app@0.2.76-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/plugin-auth-backend@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.16-next.2 + - @backstage/plugin-code-coverage-backend@0.2.3-next.2 + - @backstage/plugin-jenkins-backend@0.1.27-next.2 + - @backstage/plugin-kafka-backend@0.2.30-next.2 + - @backstage/plugin-kubernetes-backend@0.7.3-next.2 + - @backstage/plugin-proxy-backend@0.2.31-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + - @backstage/plugin-todo-backend@0.1.34-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + +## 0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.17.0-next.1 + - @backstage/plugin-search-backend@1.1.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.1 + - @backstage/plugin-kubernetes-backend@0.7.3-next.1 + - @backstage/plugin-tech-insights-backend@0.5.3-next.1 + - example-app@0.2.76-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-app-backend@0.3.37-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-azure-devops-backend@0.3.16-next.1 + - @backstage/plugin-badges-backend@0.1.31-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.3-next.1 + - @backstage/plugin-graphql-backend@0.1.27-next.1 + - @backstage/plugin-jenkins-backend@0.1.27-next.1 + - @backstage/plugin-kafka-backend@0.2.30-next.1 + - @backstage/plugin-permission-backend@0.5.12-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + - @backstage/plugin-playlist-backend@0.1.1-next.1 + - @backstage/plugin-proxy-backend@0.2.31-next.1 + - @backstage/plugin-rollbar-backend@0.1.34-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.1 + - @backstage/plugin-tech-insights-node@0.3.5-next.1 + - @backstage/plugin-techdocs-backend@1.3.1-next.1 + - @backstage/plugin-todo-backend@0.1.34-next.1 + +## 0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/plugin-auth-backend@0.17.0-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-jenkins-backend@0.1.27-next.0 + - @backstage/plugin-app-backend@0.3.37-next.0 + - @backstage/plugin-tech-insights-node@0.3.5-next.0 + - example-app@0.2.76-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-badges-backend@0.1.31-next.0 + - @backstage/plugin-code-coverage-backend@0.2.3-next.0 + - @backstage/plugin-kafka-backend@0.2.30-next.0 + - @backstage/plugin-kubernetes-backend@0.7.3-next.0 + - @backstage/plugin-playlist-backend@0.1.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.3-next.0 + - @backstage/plugin-techdocs-backend@1.3.1-next.0 + - @backstage/plugin-todo-backend@0.1.34-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/plugin-rollbar-backend@0.1.34-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-azure-devops-backend@0.3.16-next.0 + - @backstage/plugin-graphql-backend@0.1.27-next.0 + - @backstage/plugin-permission-backend@0.5.12-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-proxy-backend@0.2.31-next.0 + - @backstage/plugin-search-backend@1.0.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.0 + +## 0.2.75 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.1 + - @backstage/plugin-scaffolder-backend@1.6.0 + - @backstage/plugin-auth-node@0.2.5 + - @backstage/plugin-permission-node@0.6.5 + - @backstage/plugin-kubernetes-backend@0.7.2 + - @backstage/plugin-kafka-backend@0.2.29 + - @backstage/plugin-proxy-backend@0.2.30 + - @backstage/plugin-auth-backend@0.16.0 + - @backstage/integration@1.3.1 + - @backstage/plugin-catalog-backend@1.4.0 + - @backstage/plugin-azure-devops-backend@0.3.15 + - @backstage/plugin-search-backend-node@1.0.2 + - @backstage/plugin-tech-insights-node@0.3.4 + - @backstage/backend-tasks@0.3.5 + - @backstage/plugin-techdocs-backend@1.3.0 + - @backstage/catalog-client@1.1.0 + - @backstage/catalog-model@1.1.1 + - @backstage/config@1.0.2 + - @backstage/plugin-permission-common@0.6.4 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.4 + - @backstage/plugin-search-backend-module-pg@0.4.0 + - @backstage/plugin-jenkins-backend@0.1.26 + - @backstage/plugin-playlist-backend@0.1.0 + - @backstage/plugin-app-backend@0.3.36 + - @backstage/plugin-graphql-backend@0.1.26 + - @backstage/plugin-rollbar-backend@0.1.33 + - @backstage/plugin-code-coverage-backend@0.2.2 + - @backstage/plugin-permission-backend@0.5.11 + - @backstage/plugin-todo-backend@0.1.33 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.2 + - @backstage/plugin-tech-insights-backend@0.5.2 + - @backstage/plugin-badges-backend@0.1.30 + - example-app@0.2.75 + - @backstage/plugin-search-backend@1.0.2 + - @backstage/plugin-search-common@1.0.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.20 + +## 0.2.75-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.0-next.2 + - @backstage/catalog-model@1.1.1-next.0 + - @backstage/config@1.0.2-next.0 + - @backstage/integration@1.3.1-next.2 + - @backstage/plugin-permission-common@0.6.4-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.4-next.1 + - @backstage/plugin-catalog-backend@1.4.0-next.3 + - @backstage/plugin-auth-backend@0.16.0-next.3 + - @backstage/backend-common@0.15.1-next.3 + - @backstage/plugin-scaffolder-backend@1.6.0-next.3 + - @backstage/plugin-badges-backend@0.1.30-next.1 + - @backstage/plugin-code-coverage-backend@0.2.2-next.2 + - @backstage/plugin-jenkins-backend@0.1.26-next.3 + - @backstage/plugin-kubernetes-backend@0.7.2-next.3 + - @backstage/plugin-tech-insights-backend@0.5.2-next.2 + - @backstage/plugin-techdocs-backend@1.3.0-next.2 + - @backstage/plugin-todo-backend@0.1.33-next.2 + - example-app@0.2.75-next.3 + - @backstage/plugin-kafka-backend@0.2.29-next.1 + - @backstage/backend-tasks@0.3.5-next.1 + - @backstage/plugin-app-backend@0.3.36-next.3 + - @backstage/plugin-auth-node@0.2.5-next.3 + - @backstage/plugin-azure-devops-backend@0.3.15-next.2 + - @backstage/plugin-graphql-backend@0.1.26-next.3 + - @backstage/plugin-permission-backend@0.5.11-next.2 + - @backstage/plugin-permission-node@0.6.5-next.3 + - @backstage/plugin-proxy-backend@0.2.30-next.2 + - @backstage/plugin-rollbar-backend@0.1.33-next.3 + - @backstage/plugin-search-backend@1.0.2-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.2-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.0-next.2 + - @backstage/plugin-search-backend-node@1.0.2-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.20-next.1 + - @backstage/plugin-tech-insights-node@0.3.4-next.1 + +## 0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.7.2-next.2 + - @backstage/backend-common@0.15.1-next.2 + - @backstage/integration@1.3.1-next.1 + - @backstage/plugin-catalog-backend@1.4.0-next.2 + - @backstage/plugin-scaffolder-backend@1.6.0-next.2 + - @backstage/plugin-auth-node@0.2.5-next.2 + - @backstage/plugin-techdocs-backend@1.3.0-next.1 + - @backstage/plugin-jenkins-backend@0.1.26-next.2 + - @backstage/catalog-client@1.0.5-next.1 + - @backstage/plugin-app-backend@0.3.36-next.2 + - @backstage/plugin-auth-backend@0.16.0-next.2 + - @backstage/plugin-azure-devops-backend@0.3.15-next.1 + - @backstage/plugin-code-coverage-backend@0.2.2-next.1 + - @backstage/plugin-graphql-backend@0.1.26-next.2 + - @backstage/plugin-permission-backend@0.5.11-next.1 + - @backstage/plugin-permission-common@0.6.4-next.1 + - @backstage/plugin-permission-node@0.6.5-next.2 + - @backstage/plugin-proxy-backend@0.2.30-next.1 + - @backstage/plugin-rollbar-backend@0.1.33-next.2 + - @backstage/plugin-todo-backend@0.1.33-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.2-next.1 + - example-app@0.2.75-next.2 + +## 0.2.75-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.5-next.1 + - @backstage/plugin-permission-node@0.6.5-next.1 + - @backstage/backend-common@0.15.1-next.1 + - @backstage/plugin-catalog-backend@1.4.0-next.1 + - @backstage/plugin-auth-backend@0.16.0-next.1 + - @backstage/plugin-scaffolder-backend@1.6.0-next.1 + - @backstage/plugin-search-backend-node@1.0.2-next.1 + - @backstage/plugin-app-backend@0.3.36-next.1 + - @backstage/plugin-graphql-backend@0.1.26-next.1 + - @backstage/plugin-jenkins-backend@0.1.26-next.1 + - @backstage/plugin-rollbar-backend@0.1.33-next.1 + - @backstage/plugin-search-backend-module-pg@0.4.0-next.1 + - @backstage/plugin-kubernetes-backend@0.7.2-next.1 + - @backstage/plugin-tech-insights-backend@0.5.2-next.1 + - example-app@0.2.75-next.1 + +## 0.2.75-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + - @backstage/plugin-scaffolder-backend@1.6.0-next.0 + - @backstage/plugin-kafka-backend@0.2.29-next.0 + - @backstage/plugin-proxy-backend@0.2.30-next.0 + - @backstage/plugin-azure-devops-backend@0.3.15-next.0 + - @backstage/plugin-search-backend-node@1.0.2-next.0 + - @backstage/plugin-tech-insights-node@0.3.4-next.0 + - @backstage/backend-tasks@0.3.5-next.0 + - @backstage/plugin-catalog-backend@1.3.2-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.0-next.0 + - @backstage/catalog-client@1.0.5-next.0 + - @backstage/integration@1.3.1-next.0 + - @backstage/plugin-app-backend@0.3.36-next.0 + - @backstage/plugin-auth-backend@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.5-next.0 + - @backstage/plugin-code-coverage-backend@0.2.2-next.0 + - @backstage/plugin-graphql-backend@0.1.26-next.0 + - @backstage/plugin-jenkins-backend@0.1.26-next.0 + - @backstage/plugin-permission-backend@0.5.11-next.0 + - @backstage/plugin-permission-common@0.6.4-next.0 + - @backstage/plugin-permission-node@0.6.5-next.0 + - @backstage/plugin-rollbar-backend@0.1.33-next.0 + - @backstage/plugin-techdocs-backend@1.2.2-next.0 + - @backstage/plugin-todo-backend@0.1.33-next.0 + - @backstage/plugin-tech-insights-backend@0.5.2-next.0 + - @backstage/plugin-badges-backend@0.1.30-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.2-next.0 + - @backstage/plugin-kubernetes-backend@0.7.2-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.4-next.0 + - @backstage/plugin-search-backend@1.0.2-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.20-next.0 + - example-app@0.2.75-next.0 + - @backstage/plugin-search-common@1.0.1-next.0 + +## 0.2.74 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0 + - @backstage/plugin-kubernetes-backend@0.7.1 + - @backstage/integration@1.3.0 + - @backstage/plugin-scaffolder-backend@1.5.0 + - @backstage/plugin-auth-backend@0.15.1 + - @backstage/plugin-graphql-backend@0.1.25 + - @backstage/backend-tasks@0.3.4 + - @backstage/plugin-tech-insights-node@0.3.3 + - @backstage/plugin-catalog-backend@1.3.1 + - example-app@0.2.74 + - @backstage/plugin-app-backend@0.3.35 + - @backstage/plugin-auth-node@0.2.4 + - @backstage/plugin-azure-devops-backend@0.3.14 + - @backstage/plugin-badges-backend@0.1.29 + - @backstage/plugin-code-coverage-backend@0.2.1 + - @backstage/plugin-jenkins-backend@0.1.25 + - @backstage/plugin-kafka-backend@0.2.28 + - @backstage/plugin-permission-backend@0.5.10 + - @backstage/plugin-permission-node@0.6.4 + - @backstage/plugin-proxy-backend@0.2.29 + - @backstage/plugin-rollbar-backend@0.1.32 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.3 + - @backstage/plugin-search-backend@1.0.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.1 + - @backstage/plugin-search-backend-module-pg@0.3.6 + - @backstage/plugin-search-backend-node@1.0.1 + - @backstage/plugin-tech-insights-backend@0.5.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.19 + - @backstage/plugin-techdocs-backend@1.2.1 + - @backstage/plugin-todo-backend@0.1.32 + +## 0.2.74-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + - @backstage/integration@1.3.0-next.0 + - @backstage/plugin-scaffolder-backend@1.5.0-next.0 + - @backstage/backend-tasks@0.3.4-next.0 + - @backstage/plugin-kubernetes-backend@0.7.1-next.0 + - @backstage/plugin-tech-insights-node@0.3.3-next.0 + - @backstage/plugin-app-backend@0.3.35-next.0 + - @backstage/plugin-auth-backend@0.15.1-next.0 + - @backstage/plugin-auth-node@0.2.4-next.0 + - @backstage/plugin-azure-devops-backend@0.3.14-next.0 + - @backstage/plugin-badges-backend@0.1.29-next.0 + - @backstage/plugin-catalog-backend@1.3.1-next.0 + - @backstage/plugin-code-coverage-backend@0.2.1-next.0 + - @backstage/plugin-graphql-backend@0.1.25-next.0 + - @backstage/plugin-jenkins-backend@0.1.25-next.0 + - @backstage/plugin-kafka-backend@0.2.28-next.0 + - @backstage/plugin-permission-backend@0.5.10-next.0 + - @backstage/plugin-permission-node@0.6.4-next.0 + - @backstage/plugin-proxy-backend@0.2.29-next.0 + - @backstage/plugin-rollbar-backend@0.1.32-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.3-next.0 + - @backstage/plugin-search-backend@1.0.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.1-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.6-next.0 + - @backstage/plugin-search-backend-node@1.0.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.19-next.0 + - @backstage/plugin-techdocs-backend@1.2.1-next.0 + - @backstage/plugin-todo-backend@0.1.32-next.0 + - example-app@0.2.74-next.0 + +## 0.2.73 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-code-coverage-backend@0.2.0 + - @backstage/plugin-catalog-backend@1.3.0 + - @backstage/plugin-tech-insights-backend@0.5.0 + - @backstage/backend-common@0.14.1 + - @backstage/catalog-model@1.1.0 + - @backstage/plugin-kubernetes-backend@0.7.0 + - @backstage/plugin-search-backend@1.0.0 + - @backstage/plugin-search-backend-node@1.0.0 + - @backstage/plugin-search-common@1.0.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.0 + - @backstage/plugin-scaffolder-backend@1.4.0 + - @backstage/plugin-auth-backend@0.15.0 + - @backstage/plugin-jenkins-backend@0.1.24 + - @backstage/plugin-proxy-backend@0.2.28 + - @backstage/plugin-search-backend-module-pg@0.3.5 + - @backstage/integration@1.2.2 + - @backstage/catalog-client@1.0.4 + - @backstage/plugin-app-backend@0.3.34 + - @backstage/plugin-auth-node@0.2.3 + - @backstage/plugin-azure-devops-backend@0.3.13 + - @backstage/plugin-graphql-backend@0.1.24 + - @backstage/plugin-permission-backend@0.5.9 + - @backstage/plugin-permission-common@0.6.3 + - @backstage/plugin-permission-node@0.6.3 + - @backstage/plugin-rollbar-backend@0.1.31 + - @backstage/plugin-techdocs-backend@1.2.0 + - @backstage/plugin-todo-backend@0.1.31 + - @backstage/backend-tasks@0.3.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18 + - @backstage/plugin-tech-insights-node@0.3.2 + - @backstage/plugin-kafka-backend@0.2.27 + - @backstage/plugin-badges-backend@0.1.28 + - example-app@0.2.73 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.2 + +## 0.2.73-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-code-coverage-backend@0.2.0-next.3 + - @backstage/plugin-catalog-backend@1.3.0-next.3 + - @backstage/plugin-kubernetes-backend@0.7.0-next.3 + - @backstage/plugin-proxy-backend@0.2.28-next.1 + - @backstage/backend-common@0.14.1-next.3 + - @backstage/plugin-scaffolder-backend@1.4.0-next.3 + - @backstage/catalog-client@1.0.4-next.2 + - @backstage/integration@1.2.2-next.3 + - @backstage/plugin-app-backend@0.3.34-next.3 + - @backstage/plugin-auth-backend@0.15.0-next.3 + - @backstage/plugin-auth-node@0.2.3-next.2 + - @backstage/plugin-azure-devops-backend@0.3.13-next.1 + - @backstage/plugin-graphql-backend@0.1.24-next.1 + - @backstage/plugin-jenkins-backend@0.1.24-next.3 + - @backstage/plugin-permission-backend@0.5.9-next.2 + - @backstage/plugin-permission-common@0.6.3-next.1 + - @backstage/plugin-permission-node@0.6.3-next.2 + - @backstage/plugin-rollbar-backend@0.1.31-next.1 + - @backstage/plugin-techdocs-backend@1.2.0-next.3 + - @backstage/plugin-todo-backend@0.1.31-next.2 + - @backstage/backend-tasks@0.3.3-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18-next.2 + - @backstage/plugin-tech-insights-backend@0.5.0-next.3 + - @backstage/plugin-tech-insights-node@0.3.2-next.1 + - @backstage/catalog-model@1.1.0-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@0.2.0-next.2 + - @backstage/plugin-search-backend-node@0.6.3-next.2 + - @backstage/plugin-search-backend@0.5.4-next.2 + - example-app@0.2.73-next.3 + +## 0.2.73-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.7.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.0-next.2 + - @backstage/plugin-jenkins-backend@0.1.24-next.2 + - @backstage/plugin-search-backend-module-pg@0.3.5-next.2 + - @backstage/plugin-scaffolder-backend@1.4.0-next.2 + - @backstage/plugin-auth-backend@0.15.0-next.2 + - @backstage/catalog-model@1.1.0-next.2 + - @backstage/plugin-kafka-backend@0.2.27-next.2 + - @backstage/backend-common@0.14.1-next.2 + - @backstage/backend-tasks@0.3.3-next.2 + - @backstage/plugin-app-backend@0.3.34-next.2 + - @backstage/plugin-catalog-backend@1.2.1-next.2 + - @backstage/plugin-code-coverage-backend@0.1.32-next.2 + - @backstage/plugin-techdocs-backend@1.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.28-next.2 + - @backstage/integration@1.2.2-next.2 + - example-app@0.2.73-next.2 + +## 0.2.73-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.4.0-next.1 + - @backstage/plugin-auth-backend@0.15.0-next.1 + - @backstage/catalog-model@1.1.0-next.1 + - @backstage/backend-common@0.14.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@0.2.0-next.1 + - @backstage/plugin-catalog-backend@1.2.1-next.1 + - @backstage/plugin-techdocs-backend@1.2.0-next.1 + - example-app@0.2.73-next.1 + - @backstage/backend-tasks@0.3.3-next.1 + - @backstage/catalog-client@1.0.4-next.1 + - @backstage/integration@1.2.2-next.1 + - @backstage/plugin-app-backend@0.3.34-next.1 + - @backstage/plugin-auth-node@0.2.3-next.1 + - @backstage/plugin-badges-backend@0.1.28-next.1 + - @backstage/plugin-code-coverage-backend@0.1.32-next.1 + - @backstage/plugin-jenkins-backend@0.1.24-next.1 + - @backstage/plugin-kafka-backend@0.2.27-next.1 + - @backstage/plugin-kubernetes-backend@0.7.0-next.1 + - @backstage/plugin-permission-backend@0.5.9-next.1 + - @backstage/plugin-permission-common@0.6.3-next.0 + - @backstage/plugin-permission-node@0.6.3-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.2-next.1 + - @backstage/plugin-search-backend@0.5.4-next.1 + - @backstage/plugin-search-backend-module-pg@0.3.5-next.1 + - @backstage/plugin-search-backend-node@0.6.3-next.1 + - @backstage/plugin-tech-insights-backend@0.4.2-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18-next.1 + - @backstage/plugin-todo-backend@0.1.31-next.1 + +## 0.2.73-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-insights-backend@0.4.2-next.0 + - @backstage/backend-common@0.14.1-next.0 + - @backstage/catalog-model@1.1.0-next.0 + - @backstage/plugin-scaffolder-backend@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.14.2-next.0 + - @backstage/plugin-kubernetes-backend@0.7.0-next.0 + - @backstage/integration@1.2.2-next.0 + - @backstage/plugin-azure-devops-backend@0.3.13-next.0 + - example-app@0.2.73-next.0 + - @backstage/backend-tasks@0.3.3-next.0 + - @backstage/plugin-app-backend@0.3.34-next.0 + - @backstage/plugin-auth-node@0.2.3-next.0 + - @backstage/plugin-badges-backend@0.1.28-next.0 + - @backstage/plugin-catalog-backend@1.2.1-next.0 + - @backstage/plugin-code-coverage-backend@0.1.32-next.0 + - @backstage/plugin-graphql-backend@0.1.24-next.0 + - @backstage/plugin-jenkins-backend@0.1.24-next.0 + - @backstage/plugin-kafka-backend@0.2.27-next.0 + - @backstage/plugin-permission-backend@0.5.9-next.0 + - @backstage/plugin-permission-node@0.6.3-next.0 + - @backstage/plugin-proxy-backend@0.2.28-next.0 + - @backstage/plugin-rollbar-backend@0.1.31-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.2-next.0 + - @backstage/plugin-search-backend@0.5.4-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.6-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.5-next.0 + - @backstage/plugin-search-backend-node@0.6.3-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18-next.0 + - @backstage/plugin-tech-insights-node@0.3.2-next.0 + - @backstage/plugin-techdocs-backend@1.1.3-next.0 + - @backstage/plugin-todo-backend@0.1.31-next.0 + - @backstage/catalog-client@1.0.4-next.0 + +## 0.2.72 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-insights-backend@0.4.1 + - @backstage/plugin-catalog-backend@1.2.0 + - @backstage/plugin-auth-backend@0.14.1 + - @backstage/plugin-scaffolder-backend@1.3.0 + - @backstage/backend-tasks@0.3.2 + - @backstage/plugin-permission-node@0.6.2 + - @backstage/plugin-kubernetes-backend@0.6.0 + - @backstage/backend-common@0.14.0 + - @backstage/plugin-search-backend@0.5.3 + - @backstage/plugin-auth-node@0.2.2 + - @backstage/integration@1.2.1 + - @backstage/plugin-jenkins-backend@0.1.23 + - @backstage/plugin-search-backend-node@0.6.2 + - @backstage/catalog-client@1.0.3 + - @backstage/plugin-app-backend@0.3.33 + - @backstage/plugin-azure-devops-backend@0.3.12 + - @backstage/plugin-code-coverage-backend@0.1.31 + - @backstage/plugin-graphql-backend@0.1.23 + - @backstage/plugin-permission-backend@0.5.8 + - @backstage/plugin-permission-common@0.6.2 + - @backstage/plugin-rollbar-backend@0.1.30 + - @backstage/plugin-techdocs-backend@1.1.2 + - @backstage/plugin-todo-backend@0.1.30 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.5 + - @backstage/plugin-search-backend-module-pg@0.3.4 + - @backstage/catalog-model@1.0.3 + - @backstage/plugin-tech-insights-node@0.3.1 + - example-app@0.2.72 + - @backstage/plugin-badges-backend@0.1.27 + - @backstage/plugin-kafka-backend@0.2.26 + - @backstage/plugin-proxy-backend@0.2.27 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17 + +## 0.2.72-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.3.0-next.2 + - @backstage/backend-common@0.14.0-next.2 + - @backstage/plugin-search-backend@0.5.3-next.2 + - @backstage/plugin-auth-backend@0.14.1-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.2 + - @backstage/integration@1.2.1-next.2 + - @backstage/plugin-techdocs-backend@1.1.2-next.2 + - @backstage/plugin-search-backend-node@0.6.2-next.2 + - example-app@0.2.72-next.2 + - @backstage/backend-tasks@0.3.2-next.2 + - @backstage/plugin-app-backend@0.3.33-next.2 + - @backstage/plugin-auth-node@0.2.2-next.2 + - @backstage/plugin-azure-devops-backend@0.3.12-next.2 + - @backstage/plugin-badges-backend@0.1.27-next.2 + - @backstage/plugin-catalog-backend@1.2.0-next.2 + - @backstage/plugin-code-coverage-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.23-next.2 + - @backstage/plugin-jenkins-backend@0.1.23-next.2 + - @backstage/plugin-kafka-backend@0.2.26-next.2 + - @backstage/plugin-kubernetes-backend@0.6.0-next.2 + - @backstage/plugin-permission-backend@0.5.8-next.2 + - @backstage/plugin-permission-node@0.6.2-next.2 + - @backstage/plugin-proxy-backend@0.2.27-next.1 + - @backstage/plugin-rollbar-backend@0.1.30-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.1-next.1 + - @backstage/plugin-search-backend-module-pg@0.3.4-next.2 + - @backstage/plugin-tech-insights-backend@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17-next.1 + - @backstage/plugin-tech-insights-node@0.3.1-next.1 + - @backstage/plugin-todo-backend@0.1.30-next.2 + +## 0.2.72-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-insights-backend@0.4.1-next.1 + - @backstage/plugin-auth-backend@0.14.1-next.1 + - @backstage/plugin-jenkins-backend@0.1.23-next.1 + - @backstage/backend-tasks@0.3.2-next.1 + - @backstage/backend-common@0.13.6-next.1 + - @backstage/catalog-client@1.0.3-next.0 + - @backstage/integration@1.2.1-next.1 + - @backstage/plugin-app-backend@0.3.33-next.1 + - @backstage/plugin-auth-node@0.2.2-next.1 + - @backstage/plugin-azure-devops-backend@0.3.12-next.1 + - @backstage/plugin-catalog-backend@1.2.0-next.1 + - @backstage/plugin-code-coverage-backend@0.1.31-next.1 + - @backstage/plugin-graphql-backend@0.1.23-next.1 + - @backstage/plugin-permission-backend@0.5.8-next.1 + - @backstage/plugin-permission-common@0.6.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.1 + - @backstage/plugin-rollbar-backend@0.1.30-next.1 + - @backstage/plugin-scaffolder-backend@1.3.0-next.1 + - @backstage/plugin-techdocs-backend@1.1.2-next.1 + - @backstage/plugin-todo-backend@0.1.30-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.1 + - @backstage/plugin-search-backend-node@0.6.2-next.1 + - @backstage/catalog-model@1.0.3-next.0 + - @backstage/plugin-badges-backend@0.1.27-next.1 + - example-app@0.2.72-next.1 + - @backstage/plugin-search-backend@0.5.3-next.1 + - @backstage/plugin-kafka-backend@0.2.26-next.1 + - @backstage/plugin-kubernetes-backend@0.6.0-next.1 + - @backstage/plugin-search-backend-module-pg@0.3.4-next.1 + +## 0.2.72-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + - @backstage/plugin-kubernetes-backend@0.6.0-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-backend@0.14.1-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-techdocs-backend@1.1.2-next.0 + - example-app@0.2.72-next.0 + - @backstage/plugin-app-backend@0.3.33-next.0 + - @backstage/plugin-azure-devops-backend@0.3.12-next.0 + - @backstage/plugin-badges-backend@0.1.27-next.0 + - @backstage/plugin-code-coverage-backend@0.1.31-next.0 + - @backstage/plugin-graphql-backend@0.1.23-next.0 + - @backstage/plugin-jenkins-backend@0.1.23-next.0 + - @backstage/plugin-kafka-backend@0.2.26-next.0 + - @backstage/plugin-permission-backend@0.5.8-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + - @backstage/plugin-proxy-backend@0.2.27-next.0 + - @backstage/plugin-rollbar-backend@0.1.30-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.1-next.0 + - @backstage/plugin-search-backend@0.5.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.4-next.0 + - @backstage/plugin-tech-insights-backend@0.4.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17-next.0 + - @backstage/plugin-tech-insights-node@0.3.1-next.0 + - @backstage/plugin-todo-backend@0.1.30-next.0 + +## 0.2.71 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3 + - @backstage/plugin-auth-backend@0.14.0 + - @backstage/plugin-kubernetes-backend@0.5.1 + - @backstage/plugin-catalog-backend@1.1.2 + - @backstage/plugin-tech-insights-backend@0.4.0 + - @backstage/plugin-scaffolder-backend@1.2.0 + - @backstage/backend-tasks@0.3.1 + - @backstage/integration@1.2.0 + - @backstage/plugin-tech-insights-node@0.3.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16 + - @backstage/plugin-rollbar-backend@0.1.29 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.4 + - @backstage/config@1.0.1 + - @backstage/plugin-app-backend@0.3.32 + - @backstage/plugin-techdocs-backend@1.1.1 + - @backstage/plugin-search-backend-node@0.6.1 + - @backstage/plugin-search-backend-module-pg@0.3.3 + - @backstage/plugin-jenkins-backend@0.1.22 + - @backstage/plugin-search-backend@0.5.2 + - @backstage/plugin-auth-node@0.2.1 + - @backstage/plugin-azure-devops-backend@0.3.11 + - example-app@0.2.71 + - @backstage/catalog-client@1.0.2 + - @backstage/catalog-model@1.0.2 + - @backstage/plugin-badges-backend@0.1.26 + - @backstage/plugin-code-coverage-backend@0.1.30 + - @backstage/plugin-graphql-backend@0.1.22 + - @backstage/plugin-kafka-backend@0.2.25 + - @backstage/plugin-permission-backend@0.5.7 + - @backstage/plugin-permission-common@0.6.1 + - @backstage/plugin-permission-node@0.6.1 + - @backstage/plugin-proxy-backend@0.2.26 + - @backstage/plugin-todo-backend@0.1.29 + +## 0.2.71-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.2 + - @backstage/plugin-kubernetes-backend@0.5.1-next.1 + - @backstage/plugin-catalog-backend@1.1.2-next.2 + - @backstage/backend-tasks@0.3.1-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.0-next.1 + - @backstage/plugin-scaffolder-backend@1.2.0-next.1 + - @backstage/config@1.0.1-next.0 + - @backstage/plugin-search-backend-node@0.6.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.4-next.1 + - @backstage/plugin-search-backend-module-pg@0.3.3-next.1 + - @backstage/plugin-azure-devops-backend@0.3.11-next.1 + - example-app@0.2.71-next.2 + - @backstage/catalog-model@1.0.2-next.0 + - @backstage/integration@1.2.0-next.1 + - @backstage/plugin-app-backend@0.3.32-next.1 + - @backstage/plugin-auth-backend@0.13.1-next.2 + - @backstage/plugin-auth-node@0.2.1-next.1 + - @backstage/plugin-badges-backend@0.1.26-next.1 + - @backstage/plugin-code-coverage-backend@0.1.30-next.1 + - @backstage/plugin-graphql-backend@0.1.22-next.1 + - @backstage/plugin-jenkins-backend@0.1.22-next.1 + - @backstage/plugin-kafka-backend@0.2.25-next.1 + - @backstage/plugin-permission-backend@0.5.7-next.1 + - @backstage/plugin-permission-common@0.6.1-next.0 + - @backstage/plugin-permission-node@0.6.1-next.1 + - @backstage/plugin-proxy-backend@0.2.26-next.1 + - @backstage/plugin-rollbar-backend@0.1.29-next.2 + - @backstage/plugin-search-backend@0.5.2-next.1 + - @backstage/plugin-tech-insights-backend@0.4.0-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16-next.2 + - @backstage/plugin-tech-insights-node@0.3.0-next.2 + - @backstage/plugin-techdocs-backend@1.1.1-next.1 + - @backstage/plugin-todo-backend@0.1.29-next.1 + - @backstage/catalog-client@1.0.2-next.0 + +## 0.2.71-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.13.1-next.1 + - @backstage/plugin-tech-insights-backend@0.4.0-next.1 + - @backstage/backend-common@0.13.3-next.1 + - @backstage/plugin-tech-insights-node@0.3.0-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16-next.1 + - @backstage/plugin-catalog-backend@1.1.2-next.1 + - @backstage/plugin-rollbar-backend@0.1.29-next.1 + - example-app@0.2.71-next.1 + +## 0.2.71-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.3-next.0 + - @backstage/plugin-scaffolder-backend@1.2.0-next.0 + - @backstage/plugin-kubernetes-backend@0.5.1-next.0 + - @backstage/integration@1.2.0-next.0 + - @backstage/plugin-catalog-backend@1.1.2-next.0 + - @backstage/plugin-app-backend@0.3.32-next.0 + - @backstage/plugin-auth-backend@0.13.1-next.0 + - @backstage/plugin-rollbar-backend@0.1.29-next.0 + - @backstage/plugin-techdocs-backend@1.1.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.4-next.0 + - @backstage/plugin-jenkins-backend@0.1.22-next.0 + - @backstage/plugin-search-backend@0.5.2-next.0 + - @backstage/backend-tasks@0.3.1-next.0 + - @backstage/plugin-auth-node@0.2.1-next.0 + - example-app@0.2.71-next.0 + - @backstage/plugin-azure-devops-backend@0.3.11-next.0 + - @backstage/plugin-badges-backend@0.1.26-next.0 + - @backstage/plugin-code-coverage-backend@0.1.30-next.0 + - @backstage/plugin-graphql-backend@0.1.22-next.0 + - @backstage/plugin-kafka-backend@0.2.25-next.0 + - @backstage/plugin-permission-backend@0.5.7-next.0 + - @backstage/plugin-permission-node@0.6.1-next.0 + - @backstage/plugin-proxy-backend@0.2.26-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.7-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.3-next.0 + - @backstage/plugin-search-backend-node@0.6.1-next.0 + - @backstage/plugin-tech-insights-backend@0.3.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16-next.0 + - @backstage/plugin-tech-insights-node@0.2.10-next.0 + - @backstage/plugin-todo-backend@0.1.29-next.0 + +## 0.2.70 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0 + - @backstage/plugin-techdocs-backend@1.1.0 + - @backstage/plugin-scaffolder-backend@1.1.0 + - @backstage/integration@1.1.0 + - @backstage/plugin-search-backend@0.5.0 + - @backstage/plugin-auth-backend@0.13.0 + - @backstage/backend-tasks@0.3.0 + - @backstage/plugin-permission-common@0.6.0 + - @backstage/plugin-permission-node@0.6.0 + - @backstage/catalog-model@1.0.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15 + - @backstage/plugin-kafka-backend@0.2.24 + - @backstage/plugin-auth-node@0.2.0 + - @backstage/plugin-jenkins-backend@0.1.20 + - @backstage/plugin-badges-backend@0.1.25 + - @backstage/plugin-tech-insights-node@0.2.9 + - @backstage/plugin-todo-backend@0.1.28 + - @backstage/backend-common@0.13.2 + - @backstage/plugin-kubernetes-backend@0.5.0 + - @backstage/plugin-search-backend-node@0.6.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.3 + - @backstage/plugin-search-backend-module-pg@0.3.2 + - @backstage/plugin-permission-backend@0.5.6 + - @backstage/plugin-tech-insights-backend@0.3.0 + - @backstage/plugin-azure-devops-backend@0.3.10 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.6 + - example-app@0.2.70 + - @backstage/catalog-client@1.0.1 + - @backstage/plugin-app-backend@0.3.31 + - @backstage/plugin-code-coverage-backend@0.1.29 + - @backstage/plugin-graphql-backend@0.1.21 + - @backstage/plugin-proxy-backend@0.2.25 + - @backstage/plugin-rollbar-backend@0.1.28 + +## 0.2.70-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.13.0-next.2 + - @backstage/plugin-catalog-backend@1.1.0-next.3 + - @backstage/plugin-kafka-backend@0.2.24-next.1 + - @backstage/plugin-search-backend@0.5.0-next.2 + - @backstage/plugin-permission-common@0.6.0-next.1 + - @backstage/plugin-permission-node@0.6.0-next.2 + - @backstage/plugin-jenkins-backend@0.1.20-next.2 + - @backstage/plugin-todo-backend@0.1.28-next.2 + - @backstage/backend-common@0.13.2-next.2 + - @backstage/plugin-kubernetes-backend@0.5.0-next.1 + - @backstage/plugin-search-backend-node@0.6.0-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.2 + - @backstage/integration@1.1.0-next.2 + - @backstage/plugin-techdocs-backend@1.1.0-next.2 + - example-app@0.2.70-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.3-next.1 + - @backstage/plugin-search-backend-module-pg@0.3.2-next.1 + - @backstage/plugin-app-backend@0.3.31-next.1 + +## 0.2.70-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.1.0-next.1 + - @backstage/plugin-techdocs-backend@1.0.1-next.1 + - @backstage/plugin-scaffolder-backend@1.1.0-next.1 + - @backstage/integration@1.1.0-next.1 + - @backstage/plugin-search-backend@0.5.0-next.1 + - @backstage/backend-tasks@0.3.0-next.1 + - @backstage/plugin-permission-common@0.6.0-next.0 + - @backstage/plugin-permission-node@0.6.0-next.1 + - @backstage/plugin-badges-backend@0.1.25-next.1 + - @backstage/plugin-tech-insights-node@0.2.9-next.1 + - @backstage/plugin-permission-backend@0.5.6-next.1 + - @backstage/backend-common@0.13.2-next.1 + - @backstage/plugin-auth-backend@0.13.0-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15-next.1 + - @backstage/plugin-tech-insights-backend@0.3.0-next.1 + - @backstage/plugin-jenkins-backend@0.1.20-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.1 + - @backstage/plugin-code-coverage-backend@0.1.29-next.1 + - @backstage/plugin-todo-backend@0.1.28-next.1 + - example-app@0.2.70-next.1 + +## 0.2.70-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.0.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15-next.0 + - @backstage/plugin-search-backend@0.5.0-next.0 + - @backstage/plugin-auth-node@0.2.0-next.0 + - @backstage/plugin-auth-backend@0.13.0-next.0 + - @backstage/plugin-catalog-backend@1.0.1-next.0 + - @backstage/plugin-search-backend-node@0.5.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.3-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.2-next.0 + - @backstage/backend-common@0.13.2-next.0 + - @backstage/integration@1.0.1-next.0 + - @backstage/plugin-tech-insights-backend@0.2.11-next.0 + - @backstage/plugin-techdocs-backend@1.0.1-next.0 + - @backstage/plugin-jenkins-backend@0.1.20-next.0 + - example-app@0.2.70-next.0 + - @backstage/catalog-client@1.0.1-next.0 + - @backstage/plugin-badges-backend@0.1.25-next.0 + - @backstage/plugin-code-coverage-backend@0.1.29-next.0 + - @backstage/plugin-kafka-backend@0.2.24-next.0 + - @backstage/plugin-kubernetes-backend@0.4.14-next.0 + - @backstage/plugin-scaffolder-backend@1.0.1-next.0 + - @backstage/plugin-todo-backend@0.1.28-next.0 + - @backstage/plugin-app-backend@0.3.31-next.0 + - @backstage/plugin-permission-backend@0.5.6-next.0 + - @backstage/plugin-permission-node@0.5.6-next.0 + - @backstage/backend-tasks@0.2.2-next.0 + - @backstage/plugin-azure-devops-backend@0.3.10-next.0 + - @backstage/plugin-graphql-backend@0.1.21-next.0 + - @backstage/plugin-proxy-backend@0.2.25-next.0 + - @backstage/plugin-rollbar-backend@0.1.28-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.0 + - @backstage/plugin-tech-insights-node@0.2.9-next.0 + +## 0.2.69 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app-backend@0.3.30 + - @backstage/plugin-azure-devops-backend@0.3.9 + - @backstage/plugin-badges-backend@0.1.24 + - @backstage/plugin-catalog-backend@1.0.0 + - @backstage/plugin-jenkins-backend@0.1.19 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.5 + - @backstage/plugin-tech-insights-backend@0.2.10 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.14 + - @backstage/plugin-todo-backend@0.1.27 + - @backstage/plugin-kubernetes-backend@0.4.13 + - @backstage/plugin-scaffolder-backend@1.0.0 + - @backstage/backend-common@0.13.1 + - @backstage/backend-tasks@0.2.1 + - @backstage/plugin-auth-backend@0.12.2 + - @backstage/plugin-code-coverage-backend@0.1.28 + - @backstage/catalog-model@1.0.0 + - @backstage/integration@1.0.0 + - @backstage/catalog-client@1.0.0 + - @backstage/config@1.0.0 + - @backstage/plugin-techdocs-backend@1.0.0 + - @backstage/plugin-permission-common@0.5.3 + - @backstage/plugin-search-backend-node@0.5.2 + - example-app@0.2.69 + - @backstage/plugin-auth-node@0.1.6 + - @backstage/plugin-graphql-backend@0.1.20 + - @backstage/plugin-kafka-backend@0.2.23 + - @backstage/plugin-permission-backend@0.5.5 + - @backstage/plugin-permission-node@0.5.5 + - @backstage/plugin-proxy-backend@0.2.24 + - @backstage/plugin-rollbar-backend@0.1.27 + - @backstage/plugin-search-backend@0.4.8 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.2 + - @backstage/plugin-tech-insights-node@0.2.8 + +## 0.2.68 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0 + - @backstage/backend-tasks@0.2.0 + - @backstage/plugin-app-backend@0.3.29 + - @backstage/plugin-auth-backend@0.12.1 + - @backstage/plugin-catalog-backend@0.24.0 + - @backstage/plugin-scaffolder-backend@0.18.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.4 + - @backstage/plugin-kubernetes-backend@0.4.12 + - @backstage/plugin-rollbar-backend@0.1.26 + - @backstage/plugin-techdocs-backend@0.14.2 + - @backstage/catalog-model@0.13.0 + - @backstage/plugin-badges-backend@0.1.23 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.1 + - @backstage/plugin-search-backend-module-pg@0.3.1 + - @backstage/plugin-search-backend-node@0.5.1 + - @backstage/plugin-search-backend@0.4.7 + - @backstage/catalog-client@0.9.0 + - example-app@0.2.68 + - @backstage/plugin-auth-node@0.1.5 + - @backstage/plugin-azure-devops-backend@0.3.8 + - @backstage/plugin-code-coverage-backend@0.1.27 + - @backstage/plugin-graphql-backend@0.1.19 + - @backstage/plugin-jenkins-backend@0.1.18 + - @backstage/plugin-kafka-backend@0.2.22 + - @backstage/plugin-permission-backend@0.5.4 + - @backstage/plugin-permission-node@0.5.4 + - @backstage/plugin-proxy-backend@0.2.23 + - @backstage/plugin-tech-insights-backend@0.2.9 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.13 + - @backstage/plugin-tech-insights-node@0.2.7 + - @backstage/plugin-todo-backend@0.1.26 + +## 0.2.68-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.0-next.0 + - @backstage/backend-tasks@0.2.0-next.0 + - @backstage/plugin-app-backend@0.3.29-next.0 + - @backstage/plugin-auth-backend@0.12.1-next.0 + - @backstage/plugin-catalog-backend@0.24.0-next.0 + - @backstage/plugin-scaffolder-backend@0.18.0-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.4-next.0 + - @backstage/plugin-kubernetes-backend@0.4.12-next.0 + - @backstage/plugin-rollbar-backend@0.1.26-next.0 + - @backstage/plugin-techdocs-backend@0.14.2-next.0 + - @backstage/catalog-model@0.13.0-next.0 + - @backstage/plugin-badges-backend@0.1.23-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.1-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.1-next.0 + - @backstage/plugin-search-backend-node@0.5.1-next.0 + - @backstage/plugin-search-backend@0.4.7-next.0 + - @backstage/catalog-client@0.9.0-next.0 + - @backstage/plugin-auth-node@0.1.5-next.0 + - @backstage/plugin-azure-devops-backend@0.3.8-next.0 + - @backstage/plugin-code-coverage-backend@0.1.27-next.0 + - @backstage/plugin-graphql-backend@0.1.19-next.0 + - @backstage/plugin-jenkins-backend@0.1.18-next.0 + - @backstage/plugin-kafka-backend@0.2.22-next.0 + - @backstage/plugin-permission-backend@0.5.4-next.0 + - @backstage/plugin-permission-node@0.5.4-next.0 + - @backstage/plugin-proxy-backend@0.2.23-next.0 + - @backstage/plugin-tech-insights-backend@0.2.9-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.13-next.0 + - @backstage/plugin-tech-insights-node@0.2.7-next.0 + - @backstage/plugin-todo-backend@0.1.26-next.0 + - example-app@0.2.68-next.0 + +## 0.2.67 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@0.12.0 + - @backstage/catalog-client@0.8.0 + - @backstage/plugin-catalog-backend@0.23.0 + - @backstage/backend-common@0.12.0 + - @backstage/plugin-scaffolder-backend@0.17.3 + - @backstage/plugin-techdocs-backend@0.14.1 + - @backstage/plugin-auth-backend@0.12.0 + - @backstage/plugin-badges-backend@0.1.22 + - @backstage/plugin-code-coverage-backend@0.1.26 + - @backstage/plugin-jenkins-backend@0.1.17 + - @backstage/plugin-todo-backend@0.1.25 + - @backstage/integration@0.8.0 + - @backstage/plugin-permission-common@0.5.2 + - @backstage/plugin-permission-node@0.5.3 + - @backstage/plugin-search-backend-node@0.5.0 + - @backstage/plugin-search-backend-module-pg@0.3.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.0 + - @backstage/plugin-tech-insights-backend@0.2.8 + - example-app@0.2.67 + - @backstage/plugin-auth-node@0.1.4 + - @backstage/plugin-kafka-backend@0.2.21 + - @backstage/plugin-kubernetes-backend@0.4.11 + - @backstage/backend-tasks@0.1.10 + - @backstage/plugin-app-backend@0.3.28 + - @backstage/plugin-azure-devops-backend@0.3.7 + - @backstage/plugin-graphql-backend@0.1.18 + - @backstage/plugin-permission-backend@0.5.3 + - @backstage/plugin-proxy-backend@0.2.22 + - @backstage/plugin-rollbar-backend@0.1.25 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.3 + - @backstage/plugin-search-backend@0.4.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.12 + - @backstage/plugin-tech-insights-node@0.2.6 + +## 0.2.66 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-catalog-backend@0.22.0 + - @backstage/plugin-scaffolder-backend@0.17.0 + - @backstage/plugin-graphql-backend@0.1.17 + - @backstage/plugin-auth-backend@0.11.0 + - @backstage/plugin-kubernetes-backend@0.4.10 + - @backstage/plugin-code-coverage-backend@0.1.25 + - @backstage/plugin-jenkins-backend@0.1.16 + - @backstage/plugin-tech-insights-backend@0.2.7 + - @backstage/plugin-todo-backend@0.1.24 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/plugin-badges-backend@0.1.21 + - @backstage/backend-tasks@0.1.9 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.2 + - @backstage/plugin-techdocs-backend@0.14.0 + - @backstage/plugin-permission-node@0.5.2 + - @backstage/integration@0.7.5 + - example-app@0.2.66 + - @backstage/plugin-app-backend@0.3.27 + - @backstage/plugin-auth-node@0.1.3 + - @backstage/plugin-azure-devops-backend@0.3.6 + - @backstage/plugin-kafka-backend@0.2.20 + - @backstage/plugin-permission-backend@0.5.2 + - @backstage/plugin-proxy-backend@0.2.21 + - @backstage/plugin-rollbar-backend@0.1.24 + - @backstage/plugin-search-backend@0.4.5 + - @backstage/plugin-search-backend-module-pg@0.2.9 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.11 + - @backstage/plugin-tech-insights-node@0.2.5 + +## 0.2.66 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/backend-tasks@0.1.8 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/integration@0.7.4 + - @backstage/plugin-app-backend@0.3.26 + - @backstage/plugin-auth-backend@0.10.2 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-azure-devops-backend@0.3.5 + - @backstage/plugin-badges-backend@0.1.20 + - @backstage/plugin-catalog-backend@0.21.5 + - @backstage/plugin-code-coverage-backend@0.1.24 + - @backstage/plugin-graphql-backend@0.1.16 + - @backstage/plugin-jenkins-backend@0.1.15 + - @backstage/plugin-kafka-backend@0.2.19 + - @backstage/plugin-kubernetes-backend@0.4.9 + - @backstage/plugin-permission-backend@0.5.1 + - @backstage/plugin-permission-common@0.5.1 + - @backstage/plugin-permission-node@0.5.1 + - @backstage/plugin-proxy-backend@0.2.20 + - @backstage/plugin-rollbar-backend@0.1.23 + - @backstage/plugin-scaffolder-backend@0.16.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.1 + - @backstage/plugin-search-backend@0.4.4 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.10 + - @backstage/plugin-search-backend-module-pg@0.2.8 + - @backstage/plugin-search-backend-node@0.4.7 + - @backstage/plugin-tech-insights-backend@0.2.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.10 + - @backstage/plugin-tech-insights-node@0.2.4 + - @backstage/plugin-techdocs-backend@0.13.5 + - @backstage/plugin-todo-backend@0.1.23 + +## 0.2.65 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@0.13.4 + - @backstage/plugin-catalog-backend@0.21.4 + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/integration@0.7.3 + - @backstage/plugin-auth-backend@0.10.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-backend@0.5.0 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/plugin-rollbar-backend@0.1.22 + - @backstage/plugin-scaffolder-backend@0.16.0 + - @backstage/backend-tasks@0.1.7 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/plugin-app-backend@0.3.25 + - @backstage/plugin-azure-devops-backend@0.3.4 + - @backstage/plugin-badges-backend@0.1.19 + - @backstage/plugin-code-coverage-backend@0.1.23 + - @backstage/plugin-graphql-backend@0.1.15 + - @backstage/plugin-jenkins-backend@0.1.14 + - @backstage/plugin-kafka-backend@0.2.18 + - @backstage/plugin-kubernetes-backend@0.4.8 + - @backstage/plugin-permission-node@0.5.0 + - @backstage/plugin-proxy-backend@0.2.19 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.0 + - @backstage/plugin-search-backend@0.4.3 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.9 + - @backstage/plugin-search-backend-module-pg@0.2.7 + - @backstage/plugin-search-backend-node@0.4.6 + - @backstage/plugin-tech-insights-backend@0.2.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.9 + - @backstage/plugin-tech-insights-node@0.2.3 + - @backstage/plugin-todo-backend@0.1.22 + - example-app@0.2.65 + +## 0.2.64 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@0.6.0 + - @backstage/plugin-auth-backend@0.10.0 + - @backstage/backend-common@0.10.7 + - @backstage/backend-tasks@0.1.6 + - @backstage/plugin-app-backend@0.3.24 + - @backstage/plugin-catalog-backend@0.21.3 + - @backstage/plugin-code-coverage-backend@0.1.22 + - @backstage/plugin-scaffolder-backend@0.15.24 + - @backstage/plugin-search-backend-module-pg@0.2.6 + - @backstage/plugin-tech-insights-backend@0.2.4 + - @backstage/plugin-techdocs-backend@0.13.3 + - @backstage/plugin-auth-node@0.1.0 + - @backstage/plugin-permission-backend@0.4.3 + - @backstage/plugin-search-backend@0.4.2 + - @backstage/plugin-badges-backend@0.1.18 + - @backstage/plugin-jenkins-backend@0.1.13 + - @backstage/plugin-todo-backend@0.1.21 + - @backstage/plugin-permission-node@0.4.3 + - example-app@0.2.64 + - @backstage/plugin-azure-devops-backend@0.3.3 + - @backstage/plugin-graphql-backend@0.1.14 + - @backstage/plugin-kafka-backend@0.2.17 + - @backstage/plugin-kubernetes-backend@0.4.7 + - @backstage/plugin-proxy-backend@0.2.18 + - @backstage/plugin-rollbar-backend@0.1.21 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.8 + - @backstage/plugin-tech-insights-node@0.2.2 + +## 0.2.64-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.10.0-next.0 + - @backstage/backend-common@0.10.7-next.0 + - @backstage/backend-tasks@0.1.6-next.0 + - @backstage/plugin-app-backend@0.3.24-next.0 + - @backstage/plugin-catalog-backend@0.21.3-next.0 + - @backstage/plugin-code-coverage-backend@0.1.22-next.0 + - @backstage/plugin-scaffolder-backend@0.15.24-next.0 + - @backstage/plugin-search-backend-module-pg@0.2.6-next.0 + - @backstage/plugin-tech-insights-backend@0.2.4-next.0 + - @backstage/plugin-techdocs-backend@0.13.3-next.0 + - example-app@0.2.64-next.0 + - @backstage/plugin-azure-devops-backend@0.3.3-next.0 + - @backstage/plugin-badges-backend@0.1.18-next.0 + - @backstage/plugin-graphql-backend@0.1.14-next.0 + - @backstage/plugin-jenkins-backend@0.1.13-next.0 + - @backstage/plugin-kafka-backend@0.2.17-next.0 + - @backstage/plugin-kubernetes-backend@0.4.7-next.0 + - @backstage/plugin-permission-backend@0.4.3-next.0 + - @backstage/plugin-permission-node@0.4.3-next.0 + - @backstage/plugin-proxy-backend@0.2.18-next.0 + - @backstage/plugin-rollbar-backend@0.1.21-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.6-next.0 + - @backstage/plugin-search-backend@0.4.2-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.8-next.0 + - @backstage/plugin-tech-insights-node@0.2.2-next.0 + - @backstage/plugin-todo-backend@0.1.21-next.0 + +## 0.2.63 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0 + - @backstage/plugin-rollbar-backend@0.1.20 + - @backstage/plugin-catalog-backend@0.21.2 + - @backstage/plugin-scaffolder-backend@0.15.23 + - @backstage/plugin-proxy-backend@0.2.17 + - @backstage/backend-common@0.10.6 + - example-app@0.2.63 + - @backstage/backend-tasks@0.1.5 + - @backstage/plugin-app-backend@0.3.23 + - @backstage/plugin-azure-devops-backend@0.3.2 + - @backstage/plugin-badges-backend@0.1.17 + - @backstage/plugin-code-coverage-backend@0.1.21 + - @backstage/plugin-graphql-backend@0.1.13 + - @backstage/plugin-jenkins-backend@0.1.12 + - @backstage/plugin-kafka-backend@0.2.16 + - @backstage/plugin-kubernetes-backend@0.4.6 + - @backstage/plugin-permission-backend@0.4.2 + - @backstage/plugin-permission-node@0.4.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.5 + - @backstage/plugin-search-backend@0.4.1 + - @backstage/plugin-search-backend-module-pg@0.2.5 + - @backstage/plugin-tech-insights-backend@0.2.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.7 + - @backstage/plugin-tech-insights-node@0.2.1 + - @backstage/plugin-techdocs-backend@0.13.2 + - @backstage/plugin-todo-backend@0.1.20 + +## 0.2.63-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.1 + - @backstage/backend-common@0.10.6-next.0 + - example-app@0.2.63-next.1 + - @backstage/plugin-catalog-backend@0.21.2-next.1 + - @backstage/plugin-techdocs-backend@0.13.2-next.0 + - @backstage/backend-tasks@0.1.5-next.0 + - @backstage/plugin-app-backend@0.3.23-next.0 + - @backstage/plugin-azure-devops-backend@0.3.2-next.0 + - @backstage/plugin-badges-backend@0.1.17-next.0 + - @backstage/plugin-code-coverage-backend@0.1.21-next.0 + - @backstage/plugin-graphql-backend@0.1.13-next.0 + - @backstage/plugin-jenkins-backend@0.1.12-next.0 + - @backstage/plugin-kafka-backend@0.2.16-next.0 + - @backstage/plugin-kubernetes-backend@0.4.6-next.0 + - @backstage/plugin-permission-backend@0.4.2-next.1 + - @backstage/plugin-permission-node@0.4.2-next.1 + - @backstage/plugin-proxy-backend@0.2.17-next.1 + - @backstage/plugin-rollbar-backend@0.1.20-next.1 + - @backstage/plugin-scaffolder-backend@0.15.23-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.5-next.1 + - @backstage/plugin-search-backend@0.4.1-next.1 + - @backstage/plugin-search-backend-module-pg@0.2.5-next.0 + - @backstage/plugin-tech-insights-backend@0.2.3-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.7-next.0 + - @backstage/plugin-tech-insights-node@0.2.1-next.0 + - @backstage/plugin-todo-backend@0.1.20-next.0 + +## 0.2.63-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.9.0-next.0 + - @backstage/plugin-rollbar-backend@0.1.20-next.0 + - @backstage/plugin-catalog-backend@0.21.2-next.0 + - @backstage/plugin-scaffolder-backend@0.15.23-next.0 + - @backstage/plugin-proxy-backend@0.2.17-next.0 + - @backstage/plugin-permission-backend@0.4.2-next.0 + - @backstage/plugin-permission-node@0.4.2-next.0 + - @backstage/plugin-search-backend@0.4.1-next.0 + - example-app@0.2.63-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.5-next.0 + +## 0.2.62 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@0.4.5 + - @backstage/plugin-catalog-backend@0.21.1 + - @backstage/plugin-scaffolder-backend@0.15.22 + - @backstage/plugin-kubernetes-backend@0.4.5 + - @backstage/plugin-auth-backend@0.8.0 + - @backstage/plugin-search-backend@0.4.0 + - @backstage/plugin-tech-insights-backend@0.2.2 + - @backstage/plugin-techdocs-backend@0.13.1 + - @backstage/backend-common@0.10.5 + - example-app@0.2.62 + - @backstage/plugin-permission-backend@0.4.1 + - @backstage/plugin-permission-node@0.4.1 + +## 0.2.61 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.7.0 + - @backstage/plugin-permission-backend@0.4.0 + - @backstage/plugin-catalog-backend@0.21.0 + - @backstage/plugin-kubernetes-backend@0.4.4 + - @backstage/integration@0.7.2 + - @backstage/plugin-permission-common@0.4.0 + - @backstage/plugin-search-backend@0.3.1 + - @backstage/plugin-techdocs-backend@0.13.0 + - @backstage/backend-common@0.10.4 + - @backstage/config@0.1.13 + - @backstage/plugin-app-backend@0.3.22 + - @backstage/plugin-permission-node@0.4.0 + - @backstage/plugin-scaffolder-backend@0.15.21 + - @backstage/plugin-tech-insights-backend@0.2.0 + - @backstage/plugin-tech-insights-node@0.2.0 + - @backstage/catalog-model@0.9.10 + - example-app@0.2.61 + - @backstage/backend-tasks@0.1.4 + - @backstage/catalog-client@0.5.5 + - @backstage/plugin-azure-devops-backend@0.3.1 + - @backstage/plugin-badges-backend@0.1.16 + - @backstage/plugin-code-coverage-backend@0.1.20 + - @backstage/plugin-graphql-backend@0.1.12 + - @backstage/plugin-jenkins-backend@0.1.11 + - @backstage/plugin-kafka-backend@0.2.15 + - @backstage/plugin-proxy-backend@0.2.16 + - @backstage/plugin-rollbar-backend@0.1.19 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.4 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.8 + - @backstage/plugin-search-backend-module-pg@0.2.4 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.6 + - @backstage/plugin-todo-backend@0.1.19 + +## 0.2.61-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.7.0-next.0 + - @backstage/plugin-permission-backend@0.4.0-next.0 + - @backstage/plugin-catalog-backend@0.21.0-next.0 + - @backstage/plugin-permission-common@0.4.0-next.0 + - @backstage/backend-common@0.10.4-next.0 + - @backstage/config@0.1.13-next.0 + - @backstage/plugin-app-backend@0.3.22-next.0 + - @backstage/plugin-permission-node@0.4.0-next.0 + - @backstage/plugin-tech-insights-backend@0.2.0-next.0 + - @backstage/plugin-tech-insights-node@0.2.0-next.0 + - @backstage/catalog-model@0.9.10-next.0 + - example-app@0.2.61-next.0 + - @backstage/plugin-scaffolder-backend@0.15.21-next.0 + - @backstage/backend-tasks@0.1.4-next.0 + - @backstage/catalog-client@0.5.5-next.0 + - @backstage/integration@0.7.2-next.0 + - @backstage/plugin-azure-devops-backend@0.3.1-next.0 + - @backstage/plugin-badges-backend@0.1.16-next.0 + - @backstage/plugin-code-coverage-backend@0.1.20-next.0 + - @backstage/plugin-graphql-backend@0.1.12-next.0 + - @backstage/plugin-jenkins-backend@0.1.11-next.0 + - @backstage/plugin-kafka-backend@0.2.15-next.0 + - @backstage/plugin-kubernetes-backend@0.4.4-next.0 + - @backstage/plugin-proxy-backend@0.2.16-next.0 + - @backstage/plugin-rollbar-backend@0.1.19-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.4-next.0 + - @backstage/plugin-search-backend@0.3.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.8-next.0 + - @backstage/plugin-search-backend-module-pg@0.2.4-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.6-next.0 + - @backstage/plugin-techdocs-backend@0.12.4-next.0 + - @backstage/plugin-todo-backend@0.1.19-next.0 + +## 0.2.60 + +### Patch Changes + +- Updated dependencies + - @backstage/config@0.1.12 + - @backstage/plugin-scaffolder-backend@0.15.20 + - @backstage/integration@0.7.1 + - @backstage/backend-common@0.10.3 + - @backstage/plugin-todo-backend@0.1.18 + - @backstage/plugin-catalog-backend@0.20.0 + - @backstage/plugin-tech-insights-backend@0.1.5 + - @backstage/plugin-permission-node@0.3.0 + - @backstage/plugin-auth-backend@0.6.2 + - @backstage/plugin-code-coverage-backend@0.1.19 + - @backstage/plugin-search-backend-node@0.4.4 + - @backstage/plugin-techdocs-backend@0.12.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.5 + - @backstage/plugin-permission-backend@0.3.0 + - @backstage/plugin-graphql-backend@0.1.11 + - @backstage/plugin-kubernetes-backend@0.4.3 + - example-app@0.2.60 + - @backstage/backend-tasks@0.1.3 + - @backstage/catalog-client@0.5.4 + - @backstage/catalog-model@0.9.9 + - @backstage/plugin-badges-backend@0.1.15 + - @backstage/plugin-kafka-backend@0.2.14 + - @backstage/plugin-permission-common@0.3.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.3 + +## 0.2.59 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-rollbar-backend@0.1.18 + - @backstage/plugin-auth-backend@0.6.0 + - @backstage/backend-common@0.10.1 + - @backstage/plugin-app-backend@0.3.21 + - @backstage/plugin-catalog-backend@0.19.4 + - @backstage/plugin-scaffolder-backend@0.15.19 + - @backstage/integration@0.7.0 + - @backstage/plugin-techdocs-backend@0.12.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.4 + - @backstage/plugin-permission-backend@0.2.3 + - @backstage/plugin-permission-node@0.2.3 + - @backstage/plugin-code-coverage-backend@0.1.18 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.2 + - @backstage/plugin-todo-backend@0.1.17 + +## 0.2.58 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.1 + - @backstage/catalog-client@0.5.3 + - @backstage/plugin-rollbar-backend@0.1.17 + - @backstage/plugin-auth-backend@0.5.2 + - @backstage/plugin-permission-common@0.3.0 + - @backstage/plugin-search-backend@0.3.0 + - @backstage/plugin-techdocs-backend@0.12.1 + - @backstage/plugin-jenkins-backend@0.1.10 + - @backstage/plugin-permission-node@0.2.2 + - example-app@0.2.58 + - @backstage/plugin-app-backend@0.3.20 + - @backstage/plugin-azure-devops-backend@0.2.6 + - @backstage/plugin-badges-backend@0.1.14 + - @backstage/plugin-catalog-backend@0.19.3 + - @backstage/plugin-code-coverage-backend@0.1.17 + - @backstage/plugin-graphql-backend@0.1.10 + - @backstage/plugin-kafka-backend@0.2.13 + - @backstage/plugin-kubernetes-backend@0.4.1 + - @backstage/plugin-permission-backend@0.2.2 + - @backstage/plugin-proxy-backend@0.2.15 + - @backstage/plugin-scaffolder-backend@0.15.18 + - @backstage/plugin-search-backend-module-pg@0.2.3 + - @backstage/plugin-tech-insights-backend@0.1.4 + - @backstage/plugin-tech-insights-node@0.1.2 + - @backstage/plugin-todo-backend@0.1.16 + +## 0.2.57 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-elasticsearch@0.0.7 + - @backstage/plugin-catalog-backend@0.19.2 + - @backstage/plugin-scaffolder-backend@0.15.17 + - @backstage/backend-common@0.9.14 + - @backstage/plugin-azure-devops-backend@0.2.5 + - @backstage/plugin-auth-backend@0.5.1 + - @backstage/catalog-model@0.9.8 + - example-app@0.2.57 + +## 0.2.56 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.5.0 + - @backstage/plugin-scaffolder-backend@0.15.16 + - @backstage/plugin-kubernetes-backend@0.4.0 + - @backstage/backend-common@0.9.13 + - @backstage/plugin-catalog-backend@0.19.1 + - @backstage/plugin-search-backend@0.2.8 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.6 + - @backstage/plugin-search-backend-module-pg@0.2.2 + - @backstage/plugin-techdocs-backend@0.12.0 + - @backstage/plugin-todo-backend@0.1.15 + - @backstage/plugin-scaffolder-backend-module-rails@0.2.0 + - @backstage/plugin-azure-devops-backend@0.2.4 + - example-app@0.2.56 + +## 0.2.55 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.10 + - @backstage/plugin-scaffolder-backend@0.15.15 + - @backstage/plugin-auth-backend@0.4.10 + - @backstage/plugin-kubernetes-backend@0.3.20 + - @backstage/plugin-badges-backend@0.1.13 + - @backstage/plugin-catalog-backend@0.19.0 + - @backstage/plugin-code-coverage-backend@0.1.16 + - @backstage/plugin-jenkins-backend@0.1.9 + - @backstage/plugin-tech-insights-backend@0.1.3 + - @backstage/plugin-techdocs-backend@0.11.0 + - @backstage/plugin-todo-backend@0.1.14 + - @backstage/backend-common@0.9.12 + - @backstage/plugin-azure-devops-backend@0.2.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.2 + - @backstage/plugin-tech-insights-node@0.1.1 + - example-app@0.2.55 + +## 0.2.54 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.3.19 + - @backstage/plugin-tech-insights-backend@0.1.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.1 + - @backstage/plugin-auth-backend@0.4.9 + - @backstage/plugin-scaffolder-backend@0.15.14 + - @backstage/plugin-catalog-backend@0.18.0 + - @backstage/plugin-kafka-backend@0.2.12 + - @backstage/backend-common@0.9.11 + - @backstage/plugin-azure-devops-backend@0.2.2 + - @backstage/plugin-badges-backend@0.1.12 + - @backstage/plugin-code-coverage-backend@0.1.15 + - @backstage/plugin-jenkins-backend@0.1.8 + - @backstage/plugin-proxy-backend@0.2.14 + - @backstage/plugin-rollbar-backend@0.1.16 + - @backstage/plugin-search-backend@0.2.7 + - @backstage/plugin-techdocs-backend@0.10.9 + +## 0.2.52 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/plugin-jenkins-backend@0.1.7 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.5 + - @backstage/plugin-scaffolder-backend@0.15.12 + - @backstage/plugin-azure-devops-backend@0.2.0 + - @backstage/catalog-client@0.5.1 + - @backstage/plugin-auth-backend@0.4.7 + - @backstage/plugin-catalog-backend@0.17.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.7 + +## 0.2.50 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.4.4 + - @backstage/integration@0.6.8 + - @backstage/plugin-scaffolder-backend@0.15.8 + - @backstage/plugin-catalog-backend@0.17.0 + - @backstage/plugin-azure-devops-backend@0.1.2 + - @backstage/plugin-code-coverage-backend@0.1.13 + - @backstage/plugin-kubernetes-backend@0.3.17 + - example-app@0.2.50 + +## 0.2.49 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.16.0 + - @backstage/catalog-model@0.9.4 + - @backstage/plugin-proxy-backend@0.2.13 + - @backstage/plugin-auth-backend@0.4.3 + - @backstage/backend-common@0.9.6 + - @backstage/catalog-client@0.5.0 + - @backstage/integration@0.6.7 + - @backstage/plugin-scaffolder-backend@0.15.7 + - example-app@0.2.49 + - @backstage/plugin-badges-backend@0.1.11 + - @backstage/plugin-code-coverage-backend@0.1.12 + - @backstage/plugin-jenkins-backend@0.1.6 + - @backstage/plugin-techdocs-backend@0.10.4 + - @backstage/plugin-todo-backend@0.1.13 + +## 0.2.48 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.5 + - @backstage/plugin-catalog-backend@0.15.0 + - @backstage/plugin-azure-devops-backend@0.1.1 + - @backstage/integration@0.6.6 + - @backstage/plugin-auth-backend@0.4.2 + - example-app@0.2.48 + +## 0.2.47 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.14.0 + - @backstage/integration@0.6.5 + - @backstage/catalog-client@0.4.0 + - @backstage/catalog-model@0.9.3 + - @backstage/backend-common@0.9.4 + - @backstage/config@0.1.10 + - @backstage/plugin-kafka-backend@0.2.10 + - @backstage/plugin-kubernetes-backend@0.3.16 + - @backstage/plugin-rollbar-backend@0.1.15 + - @backstage/plugin-search-backend-module-pg@0.2.1 + - example-app@0.2.47 + - @backstage/plugin-auth-backend@0.4.1 + - @backstage/plugin-badges-backend@0.1.10 + - @backstage/plugin-code-coverage-backend@0.1.11 + - @backstage/plugin-jenkins-backend@0.1.5 + - @backstage/plugin-scaffolder-backend@0.15.6 + - @backstage/plugin-techdocs-backend@0.10.3 + - @backstage/plugin-todo-backend@0.1.12 + +## 0.2.46 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.4.0 + - @backstage/plugin-scaffolder-backend@0.15.5 + - @backstage/backend-common@0.9.3 + - @backstage/plugin-catalog-backend@0.13.8 + - @backstage/plugin-techdocs-backend@0.10.2 + - @backstage/integration@0.6.4 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.4 + - example-app@0.2.46 + +## 0.2.44 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.13.6 + - @backstage/plugin-scaffolder-backend@0.15.3 + - @backstage/plugin-techdocs-backend@0.10.1 + - @backstage/plugin-auth-backend@0.3.24 + - @backstage/integration@0.6.3 + - @backstage/plugin-search-backend@0.2.6 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.3 + - @backstage/plugin-search-backend-module-pg@0.2.0 + - @backstage/plugin-search-backend-node@0.4.2 + - @backstage/catalog-model@0.9.1 + - @backstage/backend-common@0.9.1 + - example-app@0.2.44 + +## 0.2.43 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.0 + - @backstage/plugin-catalog-backend@0.13.5 + - @backstage/plugin-search-backend-module-pg@0.1.3 + - @backstage/plugin-auth-backend@0.3.23 + - @backstage/plugin-scaffolder-backend@0.15.2 + - @backstage/integration@0.6.2 + - @backstage/config@0.1.8 + - @backstage/plugin-kubernetes-backend@0.3.15 + - @backstage/plugin-techdocs-backend@0.10.0 + - @backstage/plugin-jenkins-backend@0.1.4 + - @backstage/plugin-app-backend@0.3.16 + - @backstage/plugin-badges-backend@0.1.9 + - @backstage/plugin-code-coverage-backend@0.1.10 + - @backstage/plugin-graphql-backend@0.1.9 + - @backstage/plugin-kafka-backend@0.2.9 + - @backstage/plugin-proxy-backend@0.2.12 + - @backstage/plugin-rollbar-backend@0.1.14 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.5 + - @backstage/plugin-search-backend@0.2.5 + - @backstage/plugin-todo-backend@0.1.11 + - example-app@0.2.43 + +## 0.2.41 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.3.20 + - @backstage/integration@0.6.0 + - @backstage/plugin-scaffolder-backend@0.15.0 + - @backstage/backend-common@0.8.9 + - @backstage/plugin-kubernetes-backend@0.3.14 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.2 + - @backstage/plugin-search-backend-module-pg@0.1.1 + - @backstage/plugin-catalog-backend@0.13.2 + - @backstage/plugin-code-coverage-backend@0.1.9 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.4 + - @backstage/plugin-techdocs-backend@0.9.2 + - @backstage/plugin-todo-backend@0.1.9 + - example-app@0.2.41 + +## 0.2.38 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-backend@0.3.11 + - @backstage/catalog-client@0.3.17 + - @backstage/plugin-auth-backend@0.3.18 + - @backstage/plugin-jenkins-backend@0.1.2 + - @backstage/backend-common@0.8.7 + - @backstage/plugin-techdocs-backend@0.9.0 + - @backstage/plugin-scaffolder-backend@0.14.1 + +## 0.2.37 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.8.6 + - @backstage/plugin-scaffolder-backend@0.14.0 + - @backstage/plugin-catalog-backend@0.13.0 + - @backstage/plugin-auth-backend@0.3.17 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.3 + - @backstage/plugin-search-backend-node@0.4.0 + - @backstage/plugin-techdocs-backend@0.8.7 + - @backstage/plugin-app-backend@0.3.15 + - @backstage/plugin-kubernetes-backend@0.3.10 + - @backstage/plugin-rollbar-backend@0.1.13 + - example-app@0.2.37 + - @backstage/plugin-search-backend@0.2.3 + +## 0.2.36 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.5.8 + - @backstage/plugin-scaffolder-backend@0.13.0 + - @backstage/catalog-model@0.9.0 + - @backstage/plugin-catalog-backend@0.12.0 + - @backstage/backend-common@0.8.5 + - @backstage/plugin-search-backend-node@0.3.0 + - example-app@0.2.36 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.2 + - @backstage/catalog-client@0.3.16 + - @backstage/plugin-auth-backend@0.3.16 + - @backstage/plugin-badges-backend@0.1.8 + - @backstage/plugin-code-coverage-backend@0.1.8 + - @backstage/plugin-kafka-backend@0.2.8 + - @backstage/plugin-kubernetes-backend@0.3.9 + - @backstage/plugin-techdocs-backend@0.8.6 + - @backstage/plugin-todo-backend@0.1.8 + - @backstage/plugin-search-backend@0.2.2 + +## 0.2.35 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.12.4 + - @backstage/backend-common@0.8.4 + - @backstage/plugin-auth-backend@0.3.15 + - @backstage/plugin-catalog-backend@0.11.0 + - @backstage/plugin-techdocs-backend@0.8.5 + - @backstage/catalog-client@0.3.15 + - @backstage/plugin-kafka-backend@0.2.7 + +## 0.2.32 + +### Patch Changes + +- Updated dependencies [9c63be545] +- Updated dependencies [92963779b] +- Updated dependencies [27a9b503a] +- Updated dependencies [66c6bfebd] +- Updated dependencies [55a253de2] +- Updated dependencies [70bc30c5b] +- Updated dependencies [db1c8f93b] +- Updated dependencies [5aff84759] +- Updated dependencies [f26e6008f] +- Updated dependencies [eda9dbd5f] +- Updated dependencies [4f8cf50fe] +- Updated dependencies [875809a59] + - @backstage/plugin-catalog-backend@0.10.2 + - @backstage/backend-common@0.8.2 + - @backstage/catalog-model@0.8.2 + - @backstage/plugin-scaffolder-backend@0.12.0 + - @backstage/catalog-client@0.3.13 + - @backstage/plugin-search-backend-node@0.2.0 + - @backstage/plugin-search-backend@0.2.0 + - @backstage/plugin-proxy-backend@0.2.9 + - example-app@0.2.32 + +## 0.2.30 + +### Patch Changes + +- Updated dependencies [0fd4ea443] +- Updated dependencies [add62a455] +- Updated dependencies [260aaa684] +- Updated dependencies [704875e26] + - @backstage/plugin-catalog-backend@0.10.0 + - @backstage/catalog-client@0.3.12 + - @backstage/catalog-model@0.8.0 + - @backstage/plugin-scaffolder-backend@0.11.4 + - example-app@0.2.30 + - @backstage/plugin-auth-backend@0.3.12 + - @backstage/plugin-badges-backend@0.1.6 + - @backstage/plugin-code-coverage-backend@0.1.6 + - @backstage/plugin-kafka-backend@0.2.6 + - @backstage/plugin-kubernetes-backend@0.3.8 + - @backstage/plugin-techdocs-backend@0.8.2 + - @backstage/plugin-todo-backend@0.1.6 + +## 0.2.28 + +### Patch Changes + +- Updated dependencies [062bbf90f] +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [82ca1ac22] +- Updated dependencies [f9fb4a205] +- Updated dependencies [9a207f052] +- Updated dependencies [16be1d093] +- Updated dependencies [fd39d4662] +- Updated dependencies [f9f9d633d] + - @backstage/plugin-scaffolder-backend@0.11.1 + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + - @backstage/plugin-catalog-backend@0.9.0 + - @backstage/plugin-kubernetes-backend@0.3.7 + - example-app@0.2.28 + - @backstage/plugin-app-backend@0.3.13 + - @backstage/plugin-auth-backend@0.3.10 + - @backstage/plugin-badges-backend@0.1.4 + - @backstage/plugin-code-coverage-backend@0.1.5 + - @backstage/plugin-graphql-backend@0.1.8 + - @backstage/plugin-kafka-backend@0.2.5 + - @backstage/plugin-proxy-backend@0.2.8 + - @backstage/plugin-rollbar-backend@0.1.11 + - @backstage/plugin-search-backend@0.1.5 + - @backstage/plugin-techdocs-backend@0.8.1 + - @backstage/plugin-todo-backend@0.1.5 + +## 0.2.27 + +### Patch Changes + +- Updated dependencies [e0bfd3d44] +- Updated dependencies [e0bfd3d44] +- Updated dependencies [e0bfd3d44] +- Updated dependencies [38ca05168] +- Updated dependencies [b219821a0] +- Updated dependencies [69eefb5ae] +- Updated dependencies [f53fba29f] +- Updated dependencies [75c8cec39] +- Updated dependencies [227439a72] +- Updated dependencies [cdb3426e5] +- Updated dependencies [d8b81fd28] +- Updated dependencies [d1b1306d9] + - @backstage/plugin-scaffolder-backend@0.11.0 + - @backstage/backend-common@0.7.0 + - @backstage/plugin-techdocs-backend@0.8.0 + - @backstage/plugin-catalog-backend@0.8.2 + - @backstage/plugin-kubernetes-backend@0.3.6 + - @backstage/plugin-proxy-backend@0.2.7 + - @backstage/catalog-model@0.7.8 + - @backstage/config@0.1.5 + - @backstage/catalog-client@0.3.11 + - example-app@0.2.27 + - @backstage/plugin-app-backend@0.3.12 + - @backstage/plugin-auth-backend@0.3.9 + - @backstage/plugin-badges-backend@0.1.3 + - @backstage/plugin-code-coverage-backend@0.1.4 + - @backstage/plugin-graphql-backend@0.1.7 + - @backstage/plugin-kafka-backend@0.2.4 + - @backstage/plugin-rollbar-backend@0.1.10 + - @backstage/plugin-search-backend@0.1.4 + - @backstage/plugin-todo-backend@0.1.4 + +## 0.2.25 + +### Patch Changes + +- Updated dependencies [b9b2b4b76] +- Updated dependencies [84c54474d] +- Updated dependencies [49574a8a3] +- Updated dependencies [d367f63b5] +- Updated dependencies [5fe62f124] +- Updated dependencies [09b5fcf2e] +- Updated dependencies [55b2fc0c0] +- Updated dependencies [c42cd1daa] +- Updated dependencies [b42531cfe] +- Updated dependencies [c2306f898] + - @backstage/plugin-search-backend@0.1.3 + - @backstage/plugin-search-backend-node@0.1.3 + - @backstage/plugin-scaffolder-backend@0.10.0 + - @backstage/plugin-rollbar-backend@0.1.9 + - @backstage/backend-common@0.6.3 + - @backstage/plugin-catalog-backend@0.8.0 + - @backstage/plugin-code-coverage-backend@0.1.2 + - @backstage/plugin-kubernetes-backend@0.3.5 + - example-app@0.2.25 + +## 0.2.22 + +### Patch Changes + +- Updated dependencies [f03a52f5b] +- Updated dependencies [676ede643] +- Updated dependencies [1ac6a5233] +- Updated dependencies [2ab6f3ff0] +- Updated dependencies [0d55dcc74] +- Updated dependencies [29e1789e1] +- Updated dependencies [f1b2c1d2c] +- Updated dependencies [60e463c8d] +- Updated dependencies [676ede643] +- Updated dependencies [b196a4569] +- Updated dependencies [8488a1a96] +- Updated dependencies [37e3a69f5] +- Updated dependencies [6b2d54fd6] +- Updated dependencies [44590510d] +- Updated dependencies [164cc4c53] + - @backstage/plugin-kafka-backend@0.2.3 + - @backstage/plugin-catalog-backend@0.7.0 + - @backstage/plugin-kubernetes-backend@0.3.3 + - @backstage/plugin-scaffolder-backend@0.9.4 + - @backstage/plugin-auth-backend@0.3.7 + - @backstage/catalog-client@0.3.9 + - @backstage/plugin-todo-backend@0.1.3 + - @backstage/catalog-model@0.7.5 + - @backstage/backend-common@0.6.1 + +## 0.2.21 + +### Patch Changes + +- Updated dependencies [a2a3c7803] +- Updated dependencies [9f2e51e89] +- Updated dependencies [4d248725e] +- Updated dependencies [aaeb7ecf3] +- Updated dependencies [449776cd6] +- Updated dependencies [91e87c055] +- Updated dependencies [36d933ec5] +- Updated dependencies [113d3d59e] +- Updated dependencies [f47e11427] +- Updated dependencies [c862b3f36] + - @backstage/plugin-kubernetes-backend@0.3.2 + - @backstage/plugin-scaffolder-backend@0.9.3 + - @backstage/plugin-search-backend@0.1.2 + - @backstage/plugin-search-backend-node@0.1.2 + - @backstage/plugin-techdocs-backend@0.7.0 + - @backstage/plugin-auth-backend@0.3.6 + - @backstage/plugin-todo-backend@0.1.2 + - @backstage/plugin-catalog-backend@0.6.7 + - example-app@0.2.21 + +## 0.2.20 + +### Patch Changes + +- Updated dependencies [010aed784] +- Updated dependencies [8686eb38c] +- Updated dependencies [e7baa0d2e] +- Updated dependencies [8b4f7e42a] +- Updated dependencies [8686eb38c] +- Updated dependencies [0434853a5] +- Updated dependencies [4bc98a5b9] +- Updated dependencies [d2f4efc5d] +- Updated dependencies [8686eb38c] +- Updated dependencies [424742dc1] +- Updated dependencies [1f98a6ff8] +- Updated dependencies [8b5e59750] +- Updated dependencies [8686eb38c] + - @backstage/plugin-catalog-backend@0.6.6 + - @backstage/catalog-client@0.3.8 + - @backstage/plugin-techdocs-backend@0.6.5 + - @backstage/plugin-scaffolder-backend@0.9.2 + - @backstage/backend-common@0.6.0 + - @backstage/config@0.1.4 + - @backstage/plugin-auth-backend@0.3.5 + - @backstage/plugin-kubernetes-backend@0.3.1 + - example-app@0.2.20 + - @backstage/plugin-app-backend@0.3.10 + - @backstage/plugin-graphql-backend@0.1.6 + - @backstage/plugin-kafka-backend@0.2.2 + - @backstage/plugin-proxy-backend@0.2.6 + - @backstage/plugin-rollbar-backend@0.1.8 + - @backstage/plugin-todo-backend@0.1.1 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies [5d7834baf] +- Updated dependencies [9ef5a126d] +- Updated dependencies [d7245b733] +- Updated dependencies [393b623ae] +- Updated dependencies [d7245b733] +- Updated dependencies [0b42fff22] +- Updated dependencies [0b42fff22] +- Updated dependencies [2ef5bc7ea] +- Updated dependencies [c532c1682] +- Updated dependencies [761698831] +- Updated dependencies [aa095e469] +- Updated dependencies [761698831] +- Updated dependencies [f98f212e4] +- Updated dependencies [9581ff0b4] +- Updated dependencies [93c62c755] +- Updated dependencies [02d78290a] +- Updated dependencies [a501128db] +- Updated dependencies [8de9963f0] +- Updated dependencies [5f1b7ea35] +- Updated dependencies [2e57922de] +- Updated dependencies [e2c1b3fb6] + - @backstage/plugin-kubernetes-backend@0.3.0 + - @backstage/plugin-catalog-backend@0.6.5 + - @backstage/backend-common@0.5.6 + - @backstage/plugin-app-backend@0.3.9 + - @backstage/plugin-scaffolder-backend@0.9.1 + - @backstage/catalog-model@0.7.4 + - @backstage/catalog-client@0.3.7 + - @backstage/plugin-techdocs-backend@0.6.4 + - @backstage/plugin-auth-backend@0.3.4 + - example-app@0.2.19 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies [12d8f27a6] +- Updated dependencies [52b5bc3e2] +- Updated dependencies [ecdd407b1] +- Updated dependencies [4fbc9df79] +- Updated dependencies [12d8f27a6] +- Updated dependencies [497859088] +- Updated dependencies [1987c9341] +- Updated dependencies [f31b76b44] +- Updated dependencies [15eee03bc] +- Updated dependencies [f43192207] +- Updated dependencies [8adb48df4] +- Updated dependencies [e3adec2bd] +- Updated dependencies [9ce68b677] +- Updated dependencies [8106c9528] +- Updated dependencies [d0ed25196] +- Updated dependencies [96ccc8f69] +- Updated dependencies [3af994c81] + - @backstage/plugin-scaffolder-backend@0.9.0 + - @backstage/plugin-techdocs-backend@0.6.3 + - @backstage/plugin-catalog-backend@0.6.4 + - @backstage/plugin-kafka-backend@0.2.1 + - @backstage/catalog-model@0.7.3 + - @backstage/backend-common@0.5.5 + - @backstage/plugin-proxy-backend@0.2.5 + - @backstage/plugin-auth-backend@0.3.3 + - @backstage/plugin-kubernetes-backend@0.2.8 + - example-app@0.2.18 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies [a70af22a2] +- Updated dependencies [ec504e7b4] +- Updated dependencies [a5f42cf66] +- Updated dependencies [f37992797] +- Updated dependencies [bad21a085] +- Updated dependencies [1c06cb312] +- Updated dependencies [2499f6cde] +- Updated dependencies [a1f5e6545] + - @backstage/plugin-kubernetes-backend@0.2.7 + - @backstage/plugin-auth-backend@0.3.2 + - @backstage/plugin-scaffolder-backend@0.8.0 + - @backstage/plugin-techdocs-backend@0.6.2 + - @backstage/catalog-model@0.7.2 + - @backstage/plugin-app-backend@0.3.8 + - @backstage/plugin-catalog-backend@0.6.3 + - @backstage/config@0.1.3 + - example-app@0.2.17 + +## 0.2.15 + +### Patch Changes + +- Updated dependencies [1deb31141] +- Updated dependencies [6ed2b47d6] +- Updated dependencies [77ad0003a] +- Updated dependencies [d2441aee3] +- Updated dependencies [727f0deec] +- Updated dependencies [fb53eb7cb] +- Updated dependencies [07bafa248] +- Updated dependencies [ffffea8e6] +- Updated dependencies [f3fbfb452] +- Updated dependencies [615103a63] +- Updated dependencies [84364b35c] +- Updated dependencies [82b2c11b6] +- Updated dependencies [965e200c6] +- Updated dependencies [5a5163519] +- Updated dependencies [82b2c11b6] +- Updated dependencies [08142b256] +- Updated dependencies [08142b256] + - @backstage/plugin-auth-backend@0.3.0 + - @backstage/plugin-scaffolder-backend@0.7.0 + - @backstage/plugin-catalog-backend@0.6.1 + - @backstage/plugin-app-backend@0.3.7 + - example-app@0.2.15 + - @backstage/backend-common@0.5.3 + - @backstage/plugin-techdocs-backend@0.6.0 + +## 0.2.14 + +### Patch Changes + +- Updated dependencies [c777df180] +- Updated dependencies [2430ee7c2] +- Updated dependencies [3149bfe63] +- Updated dependencies [6e612ce25] +- Updated dependencies [e44925723] +- Updated dependencies [9d6ef14bc] +- Updated dependencies [a26668913] +- Updated dependencies [025e122c3] +- Updated dependencies [e9aab60c7] +- Updated dependencies [24e47ef1e] +- Updated dependencies [7881f2117] +- Updated dependencies [529d16d27] +- Updated dependencies [cdea0baf1] +- Updated dependencies [11cb5ef94] + - @backstage/plugin-techdocs-backend@0.5.5 + - @backstage/backend-common@0.5.2 + - @backstage/plugin-catalog-backend@0.6.0 + - @backstage/catalog-model@0.7.1 + - example-app@0.2.14 + - @backstage/plugin-scaffolder-backend@0.6.0 + - @backstage/plugin-app-backend@0.3.6 + +## 0.2.13 + +### Patch Changes + +- Updated dependencies [26a3a6cf0] +- Updated dependencies [681111228] +- Updated dependencies [664dd08c9] +- Updated dependencies [9dd057662] +- Updated dependencies [234e7d985] +- Updated dependencies [d7b1d317f] +- Updated dependencies [a91aa6bf2] +- Updated dependencies [39b05b9ae] +- Updated dependencies [4eaa06057] + - @backstage/backend-common@0.5.1 + - @backstage/plugin-scaffolder-backend@0.5.2 + - @backstage/plugin-kubernetes-backend@0.2.6 + - @backstage/plugin-catalog-backend@0.5.5 + - @backstage/plugin-kafka-backend@0.2.0 + - @backstage/plugin-auth-backend@0.2.12 + - example-app@0.2.13 + - @backstage/plugin-app-backend@0.3.5 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies [def2307f3] +- Updated dependencies [d54857099] +- Updated dependencies [0b135e7e0] +- Updated dependencies [318a6af9f] +- Updated dependencies [294a70cab] +- Updated dependencies [ac7be581a] +- Updated dependencies [0ea032763] +- Updated dependencies [5345a1f98] +- Updated dependencies [ed6baab66] +- Updated dependencies [ad838c02f] +- Updated dependencies [a5e27d5c1] +- Updated dependencies [0643a3336] +- Updated dependencies [a2291d7cc] +- Updated dependencies [f9ba00a1c] +- Updated dependencies [09a370426] +- Updated dependencies [a93f42213] + - @backstage/catalog-model@0.7.0 + - @backstage/plugin-catalog-backend@0.5.4 + - @backstage/plugin-kubernetes-backend@0.2.5 + - @backstage/backend-common@0.5.0 + - @backstage/plugin-scaffolder-backend@0.5.0 + - @backstage/plugin-techdocs-backend@0.5.4 + - @backstage/plugin-auth-backend@0.2.11 + - example-app@0.2.12 + - @backstage/plugin-kafka-backend@0.1.1 + - @backstage/plugin-app-backend@0.3.4 + - @backstage/plugin-graphql-backend@0.1.5 + - @backstage/plugin-proxy-backend@0.2.4 + - @backstage/plugin-rollbar-backend@0.1.7 + +## 0.2.11 + +### Patch Changes + +- cc068c0d6: Bump the gitbeaker dependencies to 28.x. + + To update your own installation, go through the `package.json` files of all of + your packages, and ensure that all dependencies on `@gitbeaker/node` or + `@gitbeaker/core` are at version `^28.0.2`. Then run `yarn install` at the root + of your repo. + +- Updated dependencies [68ad5af51] +- Updated dependencies [5a9a7e7c2] +- Updated dependencies [f3b064e1c] +- Updated dependencies [94fdf4955] +- Updated dependencies [cc068c0d6] +- Updated dependencies [ade6b3bdf] +- Updated dependencies [468579734] +- Updated dependencies [cb7af51e7] +- Updated dependencies [abbee6fff] +- Updated dependencies [147fadcb9] +- Updated dependencies [711ba55a2] + - @backstage/plugin-techdocs-backend@0.5.3 + - @backstage/plugin-kubernetes-backend@0.2.4 + - @backstage/catalog-model@0.6.1 + - @backstage/plugin-catalog-backend@0.5.3 + - @backstage/plugin-scaffolder-backend@0.4.1 + - @backstage/plugin-auth-backend@0.2.10 + - @backstage/backend-common@0.4.3 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies [5eb8c9b9e] +- Updated dependencies [7e3451700] + - @backstage/plugin-scaffolder-backend@0.4.0 + +## 0.2.8 + +### Patch Changes + +- 7cfcd58ee: use node 14 for backend Dockerfile +- Updated dependencies [19554f6d6] +- Updated dependencies [33a82a713] +- Updated dependencies [5de26b9a6] +- Updated dependencies [30d6c78fb] +- Updated dependencies [5084e5039] +- Updated dependencies [a8573e53b] +- Updated dependencies [aed8f7f12] + - @backstage/plugin-scaffolder-backend@0.3.6 + - @backstage/plugin-catalog-backend@0.5.1 + - @backstage/plugin-techdocs-backend@0.5.0 + - example-app@0.2.8 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies [c6eeefa35] +- Updated dependencies [fb386b760] +- Updated dependencies [c911061b7] +- Updated dependencies [7c3ffc0cd] +- Updated dependencies [dae4f3983] +- Updated dependencies [7b15cc271] +- Updated dependencies [e7496dc3e] +- Updated dependencies [1d1c2860f] +- Updated dependencies [0e6298f7e] +- Updated dependencies [8dd0a906d] +- Updated dependencies [4eafdec4a] +- Updated dependencies [6b37c95bf] +- Updated dependencies [8c31c681c] +- Updated dependencies [7b98e7fee] +- Updated dependencies [ac3560b42] +- Updated dependencies [94c65a9d4] +- Updated dependencies [0097057ed] + - @backstage/plugin-catalog-backend@0.5.0 + - @backstage/catalog-model@0.6.0 + - @backstage/plugin-techdocs-backend@0.4.0 + - @backstage/plugin-auth-backend@0.2.7 + - @backstage/backend-common@0.4.1 + - @backstage/plugin-scaffolder-backend@0.3.5 + - example-app@0.2.7 + - @backstage/plugin-kubernetes-backend@0.2.3 + +## 0.2.6 + +### Patch Changes + +- 1e22f8e0b: Unify `dockerode` library and type dependency versions +- Updated dependencies [6e8bb3ac0] +- Updated dependencies [e708679d7] +- Updated dependencies [047c018c9] +- Updated dependencies [38e24db00] +- Updated dependencies [e3bd9fc2f] +- Updated dependencies [12bbd748c] +- Updated dependencies [38d63fbe1] +- Updated dependencies [1e22f8e0b] +- Updated dependencies [83b6e0c1f] +- Updated dependencies [e3bd9fc2f] + - @backstage/plugin-catalog-backend@0.4.0 + - @backstage/backend-common@0.4.0 + - @backstage/config@0.1.2 + - @backstage/plugin-scaffolder-backend@0.3.4 + - @backstage/plugin-techdocs-backend@0.3.2 + - @backstage/catalog-model@0.5.0 + - example-app@0.2.6 + - @backstage/plugin-app-backend@0.3.3 + - @backstage/plugin-auth-backend@0.2.6 + - @backstage/plugin-graphql-backend@0.1.4 + - @backstage/plugin-kubernetes-backend@0.2.2 + - @backstage/plugin-proxy-backend@0.2.3 + - @backstage/plugin-rollbar-backend@0.1.5 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies [ae95c7ff3] +- Updated dependencies [b4488ddb0] +- Updated dependencies [612368274] +- Updated dependencies [6a6c7c14e] +- Updated dependencies [08835a61d] +- Updated dependencies [a9fd599f7] +- Updated dependencies [e42402b47] +- Updated dependencies [bcc211a08] +- Updated dependencies [3619ea4c4] + - @backstage/plugin-techdocs-backend@0.3.1 + - @backstage/plugin-catalog-backend@0.3.0 + - @backstage/backend-common@0.3.3 + - @backstage/plugin-proxy-backend@0.2.2 + - @backstage/catalog-model@0.4.0 + - @backstage/plugin-kubernetes-backend@0.2.1 + - @backstage/plugin-app-backend@0.3.2 + - example-app@0.2.5 + - @backstage/plugin-auth-backend@0.2.5 + - @backstage/plugin-scaffolder-backend@0.3.3 + +## 0.2.4 + +### Patch Changes + +- Updated dependencies [50eff1d00] +- Updated dependencies [ff1301d28] +- Updated dependencies [4b53294a6] +- Updated dependencies [3aa7efb3f] +- Updated dependencies [1ec19a3f4] +- Updated dependencies [ab94c9542] +- Updated dependencies [3a201c5d5] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] +- Updated dependencies [700a212b4] + - @backstage/plugin-auth-backend@0.2.4 + - @backstage/plugin-app-backend@0.3.1 + - @backstage/plugin-techdocs-backend@0.3.0 + - @backstage/backend-common@0.3.2 + - @backstage/plugin-catalog-backend@0.2.3 + - @backstage/catalog-model@0.3.1 + - @backstage/plugin-rollbar-backend@0.1.4 + - example-app@0.2.4 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies [1166fcc36] +- Updated dependencies [bff3305aa] +- Updated dependencies [0c2121240] +- Updated dependencies [ef2831dde] +- Updated dependencies [1185919f3] +- Updated dependencies [475fc0aaa] +- Updated dependencies [b47dce06f] +- Updated dependencies [5a1d8dca3] + - @backstage/catalog-model@0.3.0 + - @backstage/plugin-kubernetes-backend@0.2.0 + - @backstage/backend-common@0.3.1 + - @backstage/plugin-catalog-backend@0.2.2 + - @backstage/plugin-scaffolder-backend@0.3.2 + - example-app@0.2.3 + - @backstage/plugin-auth-backend@0.2.3 + - @backstage/plugin-techdocs-backend@0.2.2 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [1722cb53c] +- Updated dependencies [f531d307c] +- Updated dependencies [3efd03c0e] +- Updated dependencies [7b37e6834] +- Updated dependencies [8e2effb53] +- Updated dependencies [d33f5157c] + - @backstage/backend-common@0.3.0 + - @backstage/plugin-app-backend@0.3.0 + - @backstage/plugin-catalog-backend@0.2.1 + - example-app@0.2.2 + - @backstage/plugin-scaffolder-backend@0.3.1 + - @backstage/plugin-auth-backend@0.2.2 + - @backstage/plugin-graphql-backend@0.1.3 + - @backstage/plugin-kubernetes-backend@0.1.3 + - @backstage/plugin-proxy-backend@0.2.1 + - @backstage/plugin-rollbar-backend@0.1.3 + - @backstage/plugin-sentry-backend@0.1.3 + - @backstage/plugin-techdocs-backend@0.2.1 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies [752808090] +- Updated dependencies [462876399] +- Updated dependencies [59166e5ec] +- Updated dependencies [33b7300eb] + - @backstage/plugin-auth-backend@0.2.1 + - @backstage/plugin-scaffolder-backend@0.3.0 + - @backstage/backend-common@0.2.1 + - example-app@0.2.1 + +## 0.2.0 + +### Patch Changes + +- 440a17b39: Bump @backstage/catalog-backend and pass the now required UrlReader interface to the plugin +- 6840a68df: Pass GitHub token into Scaffolder GitHub Preparer +- 8c2b76e45: **BREAKING CHANGE** + + The existing loading of additional config files like `app-config.development.yaml` using APP_ENV or NODE_ENV has been removed. + Instead, the CLI and backend process now accept one or more `--config` flags to load config files. + + Without passing any flags, `app-config.yaml` and, if it exists, `app-config.local.yaml` will be loaded. + If passing any `--config ` flags, only those files will be loaded, **NOT** the default `app-config.yaml` one. + + The old behaviour of for example `APP_ENV=development` can be replicated using the following flags: + + ```bash + --config ../../app-config.yaml --config ../../app-config.development.yaml + ``` + +- 7bbeb049f: Change loadBackendConfig to return the config directly +- Updated dependencies [28edd7d29] +- Updated dependencies [819a70229] +- Updated dependencies [3a4236570] +- Updated dependencies [3e254503d] +- Updated dependencies [6d29605db] +- Updated dependencies [e0be86b6f] +- Updated dependencies [f70a52868] +- Updated dependencies [12b5fe940] +- Updated dependencies [5249594c5] +- Updated dependencies [56e4eb589] +- Updated dependencies [b4e5466e1] +- Updated dependencies [6f1768c0f] +- Updated dependencies [e37c0a005] +- Updated dependencies [3472c8be7] +- Updated dependencies [57d555eb2] +- Updated dependencies [61db1ddc6] +- Updated dependencies [81cb94379] +- Updated dependencies [1687b8fbb] +- Updated dependencies [a768a07fb] +- Updated dependencies [a768a07fb] +- Updated dependencies [f00ca3cb8] +- Updated dependencies [0c370c979] +- Updated dependencies [ce1f55398] +- Updated dependencies [e6b00e3af] +- Updated dependencies [9226c2aaa] +- Updated dependencies [6d97d2d6f] +- Updated dependencies [99710b102] +- Updated dependencies [6579769df] +- Updated dependencies [002860e7a] +- Updated dependencies [5adfc005e] +- Updated dependencies [33454c0f2] +- Updated dependencies [183e2a30d] +- Updated dependencies [948052cbb] +- Updated dependencies [65d722455] +- Updated dependencies [b652bf2cc] +- Updated dependencies [4036ff59d] +- Updated dependencies [991a950e0] +- Updated dependencies [512d70973] +- Updated dependencies [8c2b76e45] +- Updated dependencies [8bdf0bcf5] +- Updated dependencies [c926765a2] +- Updated dependencies [5a920c6e4] +- Updated dependencies [2f62e1804] +- Updated dependencies [440a17b39] +- Updated dependencies [fa56f4615] +- Updated dependencies [8afce088a] +- Updated dependencies [4c4eab81b] +- Updated dependencies [22ff8fba5] +- Updated dependencies [36a71d278] +- Updated dependencies [b3d57961c] +- Updated dependencies [6840a68df] +- Updated dependencies [a5cb46bac] +- Updated dependencies [49d70ccab] +- Updated dependencies [1c8c43756] +- Updated dependencies [26e69ab1a] +- Updated dependencies [5e4551e3a] +- Updated dependencies [e142a2767] +- Updated dependencies [e7f5471fd] +- Updated dependencies [e3d063ffa] +- Updated dependencies [440a17b39] +- Updated dependencies [7bbeb049f] + - @backstage/plugin-app-backend@0.2.0 + - @backstage/plugin-auth-backend@0.2.0 + - @backstage/catalog-model@0.2.0 + - @backstage/plugin-scaffolder-backend@0.2.0 + - @backstage/plugin-techdocs-backend@0.2.0 + - @backstage/plugin-catalog-backend@0.2.0 + - @backstage/plugin-proxy-backend@0.2.0 + - @backstage/backend-common@0.2.0 + - example-app@0.2.0 + - @backstage/plugin-graphql-backend@0.1.2 + - @backstage/plugin-kubernetes-backend@0.1.2 + - @backstage/plugin-rollbar-backend@0.1.2 + - @backstage/plugin-sentry-backend@0.1.2 diff --git a/packages/backend-legacy/README.md b/packages/backend-legacy/README.md new file mode 100644 index 0000000000..20e68dc746 --- /dev/null +++ b/packages/backend-legacy/README.md @@ -0,0 +1,61 @@ +# example-backend-legacy + +This package is an EXAMPLE of a Backstage backend using the old backend system. + +The main purpose of this package is to provide a test bed for Backstage plugins +that have a backend part. Feel free to experiment locally or within your fork +by adding dependencies and routes to this backend, to try things out. + +By running the `@backstage/create-app` script, you get your own separate Backstage backend. + +## Development + +To run the example backend, first go to the project root and run + +```bash +yarn install +``` + +You should only need to do this once. + +After that, go to the `packages/backend-legacy` directory and run + +```bash +yarn start +``` + +If you want to override any configuration locally, for example adding any secrets, +you can do so in `app-config.local.yaml`. + +The backend starts up on port 7007 per default. + +### Debugging + +The backend is a node process that can be inspected to allow breakpoints and live debugging. To enable this, pass the `--inspect` flag to [backend:dev](https://backstage.io/docs/local-dev/cli-build-system#backend-development). + +To debug the backend in [Visual Studio Code](https://code.visualstudio.com/): + +- Enable Auto Attach (⌘ + Shift + P > Toggle Auto Attach > Only With Flag) +- Open a VSCode terminal (Control + `) +- Run the backend from the VSCode terminal: `yarn start-backend:legacy --inspect` + +## Populating The Catalog + +If you want to use the catalog functionality, you need to add so called +locations to the backend. These are places where the backend can find some +entity descriptor data to consume and serve. For more information, see +[Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). + +For convenience we already include some statically configured example locations +in `app-config.yaml` under `catalog.locations`. For local development you can override these in your own `app-config.local.yaml`. + +## Authentication + +We chose [Passport](http://www.passportjs.org/) as authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/). + +Read more about the [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) and [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md) + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/backend-next/catalog-info.yaml b/packages/backend-legacy/catalog-info.yaml similarity index 68% rename from packages/backend-next/catalog-info.yaml rename to packages/backend-legacy/catalog-info.yaml index d297365e4b..a0acd6c653 100644 --- a/packages/backend-next/catalog-info.yaml +++ b/packages/backend-legacy/catalog-info.yaml @@ -1,8 +1,8 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: - name: example-backend-next - title: example-backend-next + name: example-backend-legacy + title: example-backend-legacy spec: lifecycle: experimental type: backstage-backend diff --git a/packages/backend-legacy/knip-report.md b/packages/backend-legacy/knip-report.md new file mode 100644 index 0000000000..f57bda79b7 --- /dev/null +++ b/packages/backend-legacy/knip-report.md @@ -0,0 +1,27 @@ +# Knip report + +## Unused dependencies (13) + +| Name | Location | Severity | +| :------------------------------------------------- | :----------- | :------- | +| @backstage/plugin-scaffolder-backend-module-gitlab | package.json | error | +| @backstage/plugin-scaffolder-backend-module-rails | package.json | error | +| @backstage/plugin-azure-sites-common | package.json | error | +| @backstage/plugin-tech-insights-node | package.json | error | +| azure-devops-node-api | package.json | error | +| pg-connection-string | package.json | error | +| @gitbeaker/node | package.json | error | +| better-sqlite3 | package.json | error | +| @octokit/rest | package.json | error | +| example-app | package.json | error | +| mysql2 | package.json | error | +| luxon | package.json | error | +| pg | package.json | error | + +## Unused devDependencies (2) + +| Name | Location | Severity | +| :------------------------------- | :----------- | :------- | +| @types/express-serve-static-core | package.json | error | +| @types/luxon | package.json | error | + diff --git a/packages/backend-next/package.json b/packages/backend-legacy/package.json similarity index 52% rename from packages/backend-next/package.json rename to packages/backend-legacy/package.json index f5c61bd8ff..97178dcb2c 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-legacy/package.json @@ -1,21 +1,24 @@ { - "name": "example-backend-next", - "version": "0.0.26-next.1", - "main": "dist/index.cjs.js", - "types": "src/index.ts", - "license": "Apache-2.0", - "private": true, + "name": "example-backend-legacy", + "version": "0.2.98-next.1", "backstage": { "role": "backend" }, + "private": true, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", - "directory": "packages/backend-next" + "directory": "packages/backend-legacy" }, - "keywords": [ - "backstage" + "license": "Apache-2.0", + "main": "dist/index.cjs.js", + "types": "src/index.ts", + "files": [ + "dist" ], "scripts": { "build": "backstage-cli package build", @@ -25,42 +28,65 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-defaults": "workspace:^", - "@backstage/backend-plugin-api": "workspace:^", + "@backstage/backend-common": "workspace:^", "@backstage/backend-tasks": "workspace:^", + "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/integration": "workspace:^", "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", - "@backstage/plugin-auth-backend-module-github-provider": "workspace:^", - "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^", - "@backstage/plugin-catalog-backend-module-openapi": "workspace:^", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-devtools-backend": "workspace:^", + "@backstage/plugin-events-backend": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", - "@backstage/plugin-notifications-backend": "workspace:^", "@backstage/plugin-permission-backend": "workspace:^", - "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "@backstage/plugin-proxy-backend": "workspace:^", "@backstage/plugin-scaffolder-backend": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-github": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^", "@backstage/plugin-search-backend": "workspace:^", "@backstage/plugin-search-backend-module-catalog": "workspace:^", + "@backstage/plugin-search-backend-module-elasticsearch": "workspace:^", "@backstage/plugin-search-backend-module-explore": "workspace:^", + "@backstage/plugin-search-backend-module-pg": "workspace:^", "@backstage/plugin-search-backend-module-techdocs": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", - "@backstage/plugin-techdocs-backend": "workspace:^" + "@backstage/plugin-signals-node": "workspace:^", + "@backstage/plugin-techdocs-backend": "workspace:^", + "@gitbeaker/node": "^35.1.0", + "@octokit/rest": "^19.0.3", + "@opentelemetry/api": "^1.4.1", + "@opentelemetry/exporter-prometheus": "^0.50.0", + "@opentelemetry/sdk-metrics": "^1.13.0", + "azure-devops-node-api": "^12.0.0", + "better-sqlite3": "^9.0.0", + "dockerode": "^4.0.0", + "example-app": "link:../app", + "express": "^4.17.1", + "express-prom-bundle": "^7.0.0", + "express-promise-router": "^4.1.0", + "luxon": "^3.0.0", + "mysql2": "^3.0.0", + "pg": "^8.11.3", + "pg-connection-string": "^2.3.0", + "prom-client": "^15.0.0", + "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "workspace:^" - }, - "files": [ - "dist" - ] + "@backstage/cli": "workspace:^", + "@types/dockerode": "^3.3.0", + "@types/express": "^4.17.6", + "@types/express-serve-static-core": "^4.17.5", + "@types/luxon": "^3.0.0" + } } diff --git a/packages/backend/prometheus.yml b/packages/backend-legacy/prometheus.yml similarity index 100% rename from packages/backend/prometheus.yml rename to packages/backend-legacy/prometheus.yml diff --git a/packages/backend/src/index.test.ts b/packages/backend-legacy/src/index.test.ts similarity index 100% rename from packages/backend/src/index.test.ts rename to packages/backend-legacy/src/index.test.ts diff --git a/packages/backend-legacy/src/index.ts b/packages/backend-legacy/src/index.ts new file mode 100644 index 0000000000..34ea8b0f6c --- /dev/null +++ b/packages/backend-legacy/src/index.ts @@ -0,0 +1,184 @@ +/* + * 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. + */ + +/* + * Hi! + * + * Note that this is an EXAMPLE Backstage backend. Please check the README. + * + * Happy hacking! + */ + +import Router from 'express-promise-router'; +import { + CacheManager, + createServiceBuilder, + DatabaseManager, + getRootLogger, + HostDiscovery, + loadBackendConfig, + notFoundHandler, + ServerTokenManager, + UrlReaders, + useHotMemoize, +} from '@backstage/backend-common'; +import { TaskScheduler } from '@backstage/backend-tasks'; +import { Config } from '@backstage/config'; +import healthcheck from './plugins/healthcheck'; +import { metricsHandler, metricsInit } from './metrics'; +import auth from './plugins/auth'; +import catalog from './plugins/catalog'; +import events from './plugins/events'; +import kubernetes from './plugins/kubernetes'; +import scaffolder from './plugins/scaffolder'; +import proxy from './plugins/proxy'; +import search from './plugins/search'; +import techdocs from './plugins/techdocs'; +import app from './plugins/app'; +import permission from './plugins/permission'; +import signals from './plugins/signals'; +import devtools from './plugins/devtools'; +import { PluginEnvironment } from './types'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +import { DefaultEventBroker } from '@backstage/plugin-events-backend'; +import { DefaultEventsService } from '@backstage/plugin-events-node'; +import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; +import { MeterProvider } from '@opentelemetry/sdk-metrics'; +import { metrics } from '@opentelemetry/api'; +import { DefaultSignalsService } from '@backstage/plugin-signals-node'; + +// Expose opentelemetry metrics using a Prometheus exporter on +// http://localhost:9464/metrics . See prometheus.yml in packages/backend for +// more information on how to scrape it. +const exporter = new PrometheusExporter(); +const meterProvider = new MeterProvider(); +metrics.setGlobalMeterProvider(meterProvider); +meterProvider.addMetricReader(exporter); + +function makeCreateEnv(config: Config) { + const root = getRootLogger(); + const reader = UrlReaders.default({ logger: root, config }); + const discovery = HostDiscovery.fromConfig(config); + const tokenManager = ServerTokenManager.fromConfig(config, { logger: root }); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); + const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); + const cacheManager = CacheManager.fromConfig(config); + const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); + const identity = DefaultIdentityClient.create({ + discovery, + }); + + const eventsService = DefaultEventsService.create({ logger: root }); + const eventBroker = new DefaultEventBroker( + root.child({ type: 'plugin' }), + eventsService, + ); + const signalsService = DefaultSignalsService.create({ + events: eventsService, + }); + + root.info(`Created UrlReader ${reader}`); + + return (plugin: string): PluginEnvironment => { + const logger = root.child({ type: 'plugin', plugin }); + const database = databaseManager.forPlugin(plugin); + const cache = cacheManager.forPlugin(plugin); + const scheduler = taskScheduler.forPlugin(plugin); + + return { + logger, + cache, + database, + config, + reader, + eventBroker, + events: eventsService, + discovery, + tokenManager, + permissions, + scheduler, + identity, + signals: signalsService, + }; + }; +} + +async function main() { + metricsInit(); + const logger = getRootLogger(); + + logger.info( + `You are running an example backend, which is supposed to be mainly used for contributing back to Backstage. ` + + `Do NOT deploy this to production. Read more here https://backstage.io/docs/getting-started/`, + ); + + const config = await loadBackendConfig({ + argv: process.argv, + logger, + }); + + const createEnv = makeCreateEnv(config); + + const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck')); + const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); + const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); + const authEnv = useHotMemoize(module, () => createEnv('auth')); + const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); + const searchEnv = useHotMemoize(module, () => createEnv('search')); + const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes')); + const appEnv = useHotMemoize(module, () => createEnv('app')); + const permissionEnv = useHotMemoize(module, () => createEnv('permission')); + const eventsEnv = useHotMemoize(module, () => createEnv('events')); + const devToolsEnv = useHotMemoize(module, () => createEnv('devtools')); + const signalsEnv = useHotMemoize(module, () => createEnv('signals')); + + const apiRouter = Router(); + apiRouter.use('/catalog', await catalog(catalogEnv)); + apiRouter.use('/events', await events(eventsEnv)); + apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); + apiRouter.use('/auth', await auth(authEnv)); + apiRouter.use('/search', await search(searchEnv)); + apiRouter.use('/techdocs', await techdocs(techdocsEnv)); + apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv)); + apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/permission', await permission(permissionEnv)); + apiRouter.use('/devtools', await devtools(devToolsEnv)); + apiRouter.use('/signals', await signals(signalsEnv)); + apiRouter.use(notFoundHandler()); + + const service = createServiceBuilder(module) + .loadConfig(config) + .addRouter('', await healthcheck(healthcheckEnv)) + .addRouter('', metricsHandler()) + .addRouter('/api', apiRouter) + .addRouter('', await app(appEnv)); + + await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); +main().catch(error => { + console.error('Backend failed to start up', error); + process.exit(1); +}); diff --git a/packages/backend/src/metrics.ts b/packages/backend-legacy/src/metrics.ts similarity index 100% rename from packages/backend/src/metrics.ts rename to packages/backend-legacy/src/metrics.ts diff --git a/packages/backend/src/plugins/DemoEventBasedEntityProvider.ts b/packages/backend-legacy/src/plugins/DemoEventBasedEntityProvider.ts similarity index 100% rename from packages/backend/src/plugins/DemoEventBasedEntityProvider.ts rename to packages/backend-legacy/src/plugins/DemoEventBasedEntityProvider.ts diff --git a/packages/backend/src/plugins/app.ts b/packages/backend-legacy/src/plugins/app.ts similarity index 100% rename from packages/backend/src/plugins/app.ts rename to packages/backend-legacy/src/plugins/app.ts diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend-legacy/src/plugins/auth.ts similarity index 100% rename from packages/backend/src/plugins/auth.ts rename to packages/backend-legacy/src/plugins/auth.ts diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend-legacy/src/plugins/catalog.ts similarity index 100% rename from packages/backend/src/plugins/catalog.ts rename to packages/backend-legacy/src/plugins/catalog.ts diff --git a/packages/backend/src/plugins/devtools.ts b/packages/backend-legacy/src/plugins/devtools.ts similarity index 100% rename from packages/backend/src/plugins/devtools.ts rename to packages/backend-legacy/src/plugins/devtools.ts diff --git a/packages/backend/src/plugins/events.ts b/packages/backend-legacy/src/plugins/events.ts similarity index 100% rename from packages/backend/src/plugins/events.ts rename to packages/backend-legacy/src/plugins/events.ts diff --git a/packages/backend/src/plugins/healthcheck.ts b/packages/backend-legacy/src/plugins/healthcheck.ts similarity index 100% rename from packages/backend/src/plugins/healthcheck.ts rename to packages/backend-legacy/src/plugins/healthcheck.ts diff --git a/packages/backend/src/plugins/kubernetes.ts b/packages/backend-legacy/src/plugins/kubernetes.ts similarity index 100% rename from packages/backend/src/plugins/kubernetes.ts rename to packages/backend-legacy/src/plugins/kubernetes.ts diff --git a/packages/backend/src/plugins/permission.ts b/packages/backend-legacy/src/plugins/permission.ts similarity index 100% rename from packages/backend/src/plugins/permission.ts rename to packages/backend-legacy/src/plugins/permission.ts diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend-legacy/src/plugins/proxy.ts similarity index 100% rename from packages/backend/src/plugins/proxy.ts rename to packages/backend-legacy/src/plugins/proxy.ts diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend-legacy/src/plugins/scaffolder.ts similarity index 100% rename from packages/backend/src/plugins/scaffolder.ts rename to packages/backend-legacy/src/plugins/scaffolder.ts diff --git a/packages/backend/src/plugins/search.ts b/packages/backend-legacy/src/plugins/search.ts similarity index 100% rename from packages/backend/src/plugins/search.ts rename to packages/backend-legacy/src/plugins/search.ts diff --git a/packages/backend/src/plugins/signals.ts b/packages/backend-legacy/src/plugins/signals.ts similarity index 100% rename from packages/backend/src/plugins/signals.ts rename to packages/backend-legacy/src/plugins/signals.ts diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend-legacy/src/plugins/techdocs.ts similarity index 100% rename from packages/backend/src/plugins/techdocs.ts rename to packages/backend-legacy/src/plugins/techdocs.ts diff --git a/packages/backend/src/types.ts b/packages/backend-legacy/src/types.ts similarity index 100% rename from packages/backend/src/types.ts rename to packages/backend-legacy/src/types.ts diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md deleted file mode 100644 index 5cc98ddb8a..0000000000 --- a/packages/backend-next/CHANGELOG.md +++ /dev/null @@ -1,2400 +0,0 @@ -# example-backend-next - -## 0.0.26-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 - - @backstage/plugin-notifications-backend@0.2.1-next.1 - - @backstage/plugin-catalog-backend@1.22.0-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 - - @backstage/plugin-scaffolder-backend@1.22.5-next.1 - - @backstage/plugin-search-backend@1.5.8-next.1 - - @backstage/backend-defaults@0.2.18-next.1 - - @backstage/plugin-app-backend@0.3.66-next.1 - - @backstage/plugin-kubernetes-backend@0.17.1-next.1 - - @backstage/backend-tasks@0.5.23-next.1 - - @backstage/plugin-auth-backend@0.22.5-next.1 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.1 - - @backstage/plugin-auth-node@0.4.13-next.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.1 - - @backstage/plugin-devtools-backend@0.3.4-next.1 - - @backstage/plugin-permission-backend@0.5.42-next.1 - - @backstage/plugin-permission-node@0.7.29-next.1 - - @backstage/plugin-proxy-backend@0.4.16-next.1 - - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.24-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 - - @backstage/plugin-search-backend-node@1.2.22-next.1 - - @backstage/plugin-signals-backend@0.1.4-next.1 - - @backstage/plugin-techdocs-backend@1.10.5-next.1 - - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.1 - - @backstage/backend-plugin-api@0.6.18-next.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.1 - -## 0.0.26-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.0 - - @backstage/plugin-catalog-backend@1.22.0-next.0 - - @backstage/plugin-scaffolder-backend@1.22.5-next.0 - - @backstage/catalog-model@1.5.0-next.0 - - @backstage/plugin-search-backend-node@1.2.22-next.0 - - @backstage/plugin-search-backend@1.5.8-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.0 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.23-next.0 - - @backstage/plugin-auth-backend@0.22.5-next.0 - - @backstage/plugin-auth-node@0.4.13-next.0 - - @backstage/plugin-notifications-backend@0.2.1-next.0 - - @backstage/backend-plugin-api@0.6.18-next.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.0 - - @backstage/backend-defaults@0.2.18-next.0 - - @backstage/plugin-app-backend@0.3.66-next.0 - - @backstage/plugin-kubernetes-backend@0.17.1-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 - - @backstage/plugin-techdocs-backend@1.10.5-next.0 - - @backstage/backend-tasks@0.5.23-next.0 - - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.0 - - @backstage/plugin-devtools-backend@0.3.4-next.0 - - @backstage/plugin-permission-backend@0.5.42-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.0 - - @backstage/plugin-permission-common@0.7.13 - - @backstage/plugin-permission-node@0.7.29-next.0 - - @backstage/plugin-proxy-backend@0.4.16-next.0 - - @backstage/plugin-signals-backend@0.1.4-next.0 - -## 0.0.25 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-badges-backend@0.4.0 - - @backstage/plugin-kubernetes-backend@0.17.0 - - @backstage/plugin-azure-devops-backend@0.6.4 - - @backstage/plugin-techdocs-backend@1.10.4 - - @backstage/plugin-notifications-backend@0.2.0 - - @backstage/plugin-permission-node@0.7.28 - - @backstage/plugin-auth-backend@0.22.4 - - @backstage/plugin-catalog-backend@1.21.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.0 - - @backstage/backend-plugin-api@0.6.17 - - @backstage/plugin-search-backend@1.5.7 - - @backstage/plugin-todo-backend@0.3.16 - - @backstage/plugin-scaffolder-backend-module-github@0.2.7 - - @backstage/plugin-search-backend-module-techdocs@0.1.22 - - @backstage/plugin-search-backend-module-explore@0.1.21 - - @backstage/plugin-entity-feedback-backend@0.2.14 - - @backstage/plugin-search-backend-node@1.2.21 - - @backstage/plugin-lighthouse-backend@0.4.10 - - @backstage/plugin-permission-backend@0.5.41 - - @backstage/plugin-sonarqube-backend@0.2.19 - - @backstage/plugin-devtools-backend@0.3.3 - - @backstage/plugin-linguist-backend@0.5.15 - - @backstage/plugin-playlist-backend@0.3.21 - - @backstage/plugin-jenkins-backend@0.4.4 - - @backstage/backend-tasks@0.5.22 - - @backstage/plugin-nomad-backend@0.1.19 - - @backstage/plugin-adr-backend@0.4.14 - - @backstage/plugin-app-backend@0.3.65 - - @backstage/plugin-auth-node@0.4.12 - - @backstage/plugin-signals-backend@0.1.3 - - @backstage/plugin-proxy-backend@0.4.15 - - @backstage/plugin-scaffolder-backend@1.22.4 - - @backstage/backend-defaults@0.2.17 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.3 - - @backstage/plugin-catalog-backend-module-openapi@0.1.35 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4 - - @backstage/plugin-search-backend-module-catalog@0.1.22 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.14 - - @backstage/catalog-model@1.4.5 - - @backstage/plugin-auth-backend-module-github-provider@0.1.14 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15 - - @backstage/plugin-permission-common@0.7.13 - -## 0.0.25-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.17.0-next.1 - - @backstage/plugin-azure-devops-backend@0.6.4-next.1 - - @backstage/plugin-techdocs-backend@1.10.4-next.1 - - @backstage/plugin-auth-backend@0.22.4-next.1 - - @backstage/backend-plugin-api@0.6.17-next.1 - - @backstage/plugin-auth-node@0.4.12-next.1 - - @backstage/plugin-proxy-backend@0.4.15-next.1 - - @backstage/plugin-scaffolder-backend@1.22.4-next.1 - - @backstage/plugin-catalog-backend@1.21.1-next.1 - - @backstage/plugin-scaffolder-backend-module-github@0.2.7-next.1 - - @backstage/plugin-app-backend@0.3.65-next.1 - - @backstage/plugin-notifications-backend@0.2.0-next.1 - - @backstage/backend-defaults@0.2.17-next.1 - - @backstage/backend-tasks@0.5.22-next.1 - - @backstage/plugin-adr-backend@0.4.14-next.1 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.3-next.1 - - @backstage/plugin-badges-backend@0.3.14-next.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.35-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4-next.1 - - @backstage/plugin-devtools-backend@0.3.3-next.1 - - @backstage/plugin-entity-feedback-backend@0.2.14-next.1 - - @backstage/plugin-jenkins-backend@0.4.4-next.1 - - @backstage/plugin-lighthouse-backend@0.4.10-next.1 - - @backstage/plugin-linguist-backend@0.5.15-next.1 - - @backstage/plugin-nomad-backend@0.1.19-next.1 - - @backstage/plugin-permission-backend@0.5.41-next.1 - - @backstage/plugin-permission-node@0.7.28-next.1 - - @backstage/plugin-playlist-backend@0.3.21-next.1 - - @backstage/plugin-search-backend@1.5.7-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.22-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.21-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.22-next.1 - - @backstage/plugin-search-backend-node@1.2.21-next.1 - - @backstage/plugin-signals-backend@0.1.3-next.1 - - @backstage/plugin-sonarqube-backend@0.2.19-next.1 - - @backstage/plugin-todo-backend@0.3.16-next.1 - - @backstage/catalog-model@1.4.5 - - @backstage/plugin-auth-backend-module-github-provider@0.1.14-next.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.11-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.14-next.1 - - @backstage/plugin-permission-common@0.7.13 - -## 0.0.25-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-techdocs-backend@1.10.4-next.0 - - @backstage/plugin-catalog-backend@1.21.1-next.0 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.11-next.0 - - @backstage/plugin-kubernetes-backend@0.16.4-next.0 - - @backstage/plugin-signals-backend@0.1.3-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.22-next.0 - - @backstage/plugin-scaffolder-backend@1.22.4-next.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.35-next.0 - - @backstage/backend-defaults@0.2.17-next.0 - - @backstage/plugin-app-backend@0.3.65-next.0 - - @backstage/backend-plugin-api@0.6.17-next.0 - - @backstage/backend-tasks@0.5.22-next.0 - - @backstage/catalog-model@1.4.5 - - @backstage/plugin-adr-backend@0.4.14-next.0 - - @backstage/plugin-auth-backend@0.22.4-next.0 - - @backstage/plugin-auth-backend-module-github-provider@0.1.14-next.0 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.3-next.0 - - @backstage/plugin-auth-node@0.4.12-next.0 - - @backstage/plugin-azure-devops-backend@0.6.4-next.0 - - @backstage/plugin-badges-backend@0.3.14-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4-next.0 - - @backstage/plugin-devtools-backend@0.3.3-next.0 - - @backstage/plugin-entity-feedback-backend@0.2.14-next.0 - - @backstage/plugin-jenkins-backend@0.4.4-next.0 - - @backstage/plugin-lighthouse-backend@0.4.10-next.0 - - @backstage/plugin-linguist-backend@0.5.15-next.0 - - @backstage/plugin-nomad-backend@0.1.19-next.0 - - @backstage/plugin-notifications-backend@0.1.3-next.0 - - @backstage/plugin-permission-backend@0.5.41-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.14-next.0 - - @backstage/plugin-permission-common@0.7.13 - - @backstage/plugin-permission-node@0.7.28-next.0 - - @backstage/plugin-playlist-backend@0.3.21-next.0 - - @backstage/plugin-proxy-backend@0.4.15-next.0 - - @backstage/plugin-scaffolder-backend-module-github@0.2.7-next.0 - - @backstage/plugin-search-backend@1.5.7-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.22-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.21-next.0 - - @backstage/plugin-search-backend-node@1.2.21-next.0 - - @backstage/plugin-sonarqube-backend@0.2.19-next.0 - - @backstage/plugin-todo-backend@0.3.16-next.0 - -## 0.0.24 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.21.0 - - @backstage/plugin-kubernetes-backend@0.16.3 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.3 - - @backstage/plugin-permission-backend@0.5.40 - - @backstage/plugin-proxy-backend@0.4.14 - - @backstage/plugin-scaffolder-backend@1.22.3 - - @backstage/plugin-jenkins-backend@0.4.3 - - @backstage/plugin-auth-backend@0.22.3 - - @backstage/plugin-auth-node@0.4.11 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.2 - - @backstage/plugin-catalog-backend-module-openapi@0.1.34 - - @backstage/plugin-azure-devops-backend@0.6.3 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.10 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.14 - - @backstage/plugin-lighthouse-backend@0.4.9 - - @backstage/plugin-linguist-backend@0.5.14 - - @backstage/plugin-search-backend-module-catalog@0.1.21 - - @backstage/plugin-search-backend-module-techdocs@0.1.21 - - @backstage/plugin-todo-backend@0.3.15 - - @backstage/backend-defaults@0.2.16 - - @backstage/plugin-app-backend@0.3.64 - - @backstage/plugin-adr-backend@0.4.13 - - @backstage/plugin-badges-backend@0.3.13 - - @backstage/plugin-entity-feedback-backend@0.2.13 - - @backstage/plugin-notifications-backend@0.1.2 - - @backstage/plugin-playlist-backend@0.3.20 - - @backstage/plugin-techdocs-backend@1.10.3 - - @backstage/plugin-auth-backend-module-github-provider@0.1.13 - - @backstage/backend-plugin-api@0.6.16 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.13 - - @backstage/plugin-permission-node@0.7.27 - - @backstage/plugin-signals-backend@0.1.2 - - @backstage/backend-tasks@0.5.21 - - @backstage/plugin-devtools-backend@0.3.2 - - @backstage/plugin-nomad-backend@0.1.18 - - @backstage/plugin-scaffolder-backend-module-github@0.2.6 - - @backstage/plugin-search-backend@1.5.6 - - @backstage/plugin-search-backend-module-explore@0.1.20 - - @backstage/plugin-search-backend-node@1.2.20 - - @backstage/plugin-sonarqube-backend@0.2.18 - - @backstage/catalog-model@1.4.5 - - @backstage/plugin-permission-common@0.7.13 - -## 0.0.23 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.20.0 - - @backstage/plugin-kubernetes-backend@0.16.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.2 - - @backstage/plugin-permission-backend@0.5.39 - - @backstage/plugin-catalog-backend-module-openapi@0.1.33 - - @backstage/plugin-auth-backend@0.22.2 - - @backstage/plugin-azure-devops-backend@0.6.2 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.9 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.13 - - @backstage/plugin-jenkins-backend@0.4.2 - - @backstage/plugin-lighthouse-backend@0.4.8 - - @backstage/plugin-linguist-backend@0.5.13 - - @backstage/plugin-scaffolder-backend@1.22.2 - - @backstage/plugin-search-backend-module-catalog@0.1.20 - - @backstage/plugin-search-backend-module-techdocs@0.1.20 - - @backstage/plugin-todo-backend@0.3.14 - - @backstage/backend-defaults@0.2.15 - - @backstage/plugin-app-backend@0.3.63 - - @backstage/plugin-adr-backend@0.4.12 - - @backstage/plugin-auth-node@0.4.10 - - @backstage/plugin-badges-backend@0.3.12 - - @backstage/plugin-entity-feedback-backend@0.2.12 - - @backstage/plugin-notifications-backend@0.1.1 - - @backstage/plugin-playlist-backend@0.3.19 - - @backstage/plugin-techdocs-backend@1.10.2 - - @backstage/backend-tasks@0.5.20 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.1 - - @backstage/plugin-devtools-backend@0.3.1 - - @backstage/plugin-nomad-backend@0.1.17 - - @backstage/plugin-permission-node@0.7.26 - - @backstage/plugin-proxy-backend@0.4.13 - - @backstage/plugin-scaffolder-backend-module-github@0.2.5 - - @backstage/plugin-search-backend@1.5.5 - - @backstage/plugin-search-backend-module-explore@0.1.19 - - @backstage/plugin-search-backend-node@1.2.19 - - @backstage/plugin-signals-backend@0.1.1 - - @backstage/plugin-sonarqube-backend@0.2.17 - - @backstage/backend-plugin-api@0.6.15 - - @backstage/catalog-model@1.4.5 - - @backstage/plugin-auth-backend-module-github-provider@0.1.12 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.12 - - @backstage/plugin-permission-common@0.7.13 - -## 0.0.22 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.19.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.1 - - @backstage/plugin-permission-backend@0.5.38 - - @backstage/plugin-catalog-backend-module-openapi@0.1.32 - - @backstage/plugin-auth-backend@0.22.1 - - @backstage/plugin-azure-devops-backend@0.6.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.8 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.12 - - @backstage/plugin-jenkins-backend@0.4.1 - - @backstage/plugin-kubernetes-backend@0.16.1 - - @backstage/plugin-lighthouse-backend@0.4.7 - - @backstage/plugin-linguist-backend@0.5.12 - - @backstage/plugin-scaffolder-backend@1.22.1 - - @backstage/plugin-search-backend-module-catalog@0.1.19 - - @backstage/plugin-search-backend-module-techdocs@0.1.19 - - @backstage/plugin-todo-backend@0.3.13 - - @backstage/plugin-auth-backend-module-github-provider@0.1.11 - - @backstage/plugin-techdocs-backend@1.10.1 - -## 0.0.21 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-notifications-backend@0.1.0 - - @backstage/plugin-scaffolder-backend@1.22.0 - - @backstage/plugin-linguist-backend@0.5.11 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.7 - - @backstage/plugin-catalog-backend-module-unprocessed@0.4.0 - - @backstage/plugin-catalog-backend@1.18.0 - - @backstage/plugin-devtools-backend@0.3.0 - - @backstage/plugin-jenkins-backend@0.4.0 - - @backstage/plugin-search-backend@1.5.4 - - @backstage/plugin-auth-node@0.4.9 - - @backstage/plugin-lighthouse-backend@0.4.6 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.0 - - @backstage/plugin-azure-devops-backend@0.6.0 - - @backstage/plugin-permission-backend@0.5.37 - - @backstage/plugin-signals-backend@0.1.0 - - @backstage/plugin-nomad-backend@0.1.16 - - @backstage/plugin-entity-feedback-backend@0.2.11 - - @backstage/plugin-playlist-backend@0.3.18 - - @backstage/backend-plugin-api@0.6.14 - - @backstage/plugin-auth-backend@0.22.0 - - @backstage/plugin-techdocs-backend@1.10.0 - - @backstage/plugin-scaffolder-backend-module-github@0.2.4 - - @backstage/plugin-permission-common@0.7.13 - - @backstage/plugin-search-backend-module-techdocs@0.1.18 - - @backstage/plugin-search-backend-module-catalog@0.1.18 - - @backstage/plugin-search-backend-module-explore@0.1.18 - - @backstage/backend-defaults@0.2.14 - - @backstage/plugin-kubernetes-backend@0.16.0 - - @backstage/plugin-adr-backend@0.4.11 - - @backstage/plugin-proxy-backend@0.4.12 - - @backstage/backend-tasks@0.5.19 - - @backstage/plugin-search-backend-node@1.2.18 - - @backstage/plugin-app-backend@0.3.62 - - @backstage/plugin-permission-node@0.7.25 - - @backstage/plugin-todo-backend@0.3.12 - - @backstage/plugin-badges-backend@0.3.11 - - @backstage/plugin-auth-backend-module-github-provider@0.1.11 - - @backstage/plugin-catalog-backend-module-openapi@0.1.31 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.11 - - @backstage/plugin-sonarqube-backend@0.2.16 - - @backstage/catalog-model@1.4.5 - -## 0.0.21-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.22.0-next.2 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.7-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.11-next.2 - - @backstage/plugin-catalog-backend@1.18.0-next.2 - - @backstage/plugin-devtools-backend@0.3.0-next.2 - - @backstage/plugin-jenkins-backend@0.4.0-next.2 - - @backstage/plugin-search-backend@1.5.4-next.2 - - @backstage/plugin-techdocs-backend@1.10.0-next.2 - - @backstage/plugin-notifications-backend@0.1.0-next.2 - - @backstage/plugin-linguist-backend@0.5.11-next.2 - - @backstage/plugin-kubernetes-backend@0.16.0-next.2 - - @backstage/plugin-todo-backend@0.3.12-next.2 - - @backstage/plugin-signals-backend@0.1.0-next.2 - - @backstage/plugin-scaffolder-backend-module-github@0.2.4-next.2 - - @backstage/plugin-catalog-backend-module-openapi@0.1.31-next.2 - - @backstage/plugin-adr-backend@0.4.11-next.2 - - @backstage/plugin-azure-devops-backend@0.6.0-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.18-next.2 - - @backstage/plugin-auth-backend@0.22.0-next.2 - - @backstage/backend-defaults@0.2.14-next.2 - - @backstage/plugin-app-backend@0.3.62-next.2 - - @backstage/plugin-auth-node@0.4.9-next.2 - - @backstage/plugin-badges-backend@0.3.11-next.2 - - @backstage/plugin-entity-feedback-backend@0.2.11-next.2 - - @backstage/plugin-lighthouse-backend@0.4.6-next.2 - - @backstage/plugin-playlist-backend@0.3.18-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.18-next.2 - - @backstage/backend-plugin-api@0.6.14-next.2 - - @backstage/backend-tasks@0.5.19-next.2 - - @backstage/catalog-model@1.4.5-next.0 - - @backstage/plugin-auth-backend-module-github-provider@0.1.11-next.2 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.0-next.2 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11-next.2 - - @backstage/plugin-nomad-backend@0.1.16-next.2 - - @backstage/plugin-permission-backend@0.5.37-next.2 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.11-next.2 - - @backstage/plugin-permission-common@0.7.13-next.1 - - @backstage/plugin-permission-node@0.7.25-next.2 - - @backstage/plugin-proxy-backend@0.4.12-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.18-next.2 - - @backstage/plugin-search-backend-node@1.2.18-next.2 - - @backstage/plugin-sonarqube-backend@0.2.16-next.2 - -## 0.0.21-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-entity-feedback-backend@0.2.11-next.1 - - @backstage/plugin-notifications-backend@0.1.0-next.1 - - @backstage/plugin-scaffolder-backend@1.22.0-next.1 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.0-next.1 - - @backstage/plugin-scaffolder-backend-module-github@0.2.4-next.1 - - @backstage/plugin-app-backend@0.3.62-next.1 - - @backstage/plugin-signals-backend@0.1.0-next.1 - - @backstage/plugin-azure-devops-backend@0.6.0-next.1 - - @backstage/plugin-kubernetes-backend@0.16.0-next.1 - - @backstage/backend-plugin-api@0.6.14-next.1 - - @backstage/backend-tasks@0.5.19-next.1 - - @backstage/plugin-adr-backend@0.4.11-next.1 - - @backstage/plugin-auth-backend@0.22.0-next.1 - - @backstage/plugin-auth-node@0.4.9-next.1 - - @backstage/plugin-badges-backend@0.3.11-next.1 - - @backstage/plugin-catalog-backend@1.18.0-next.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.7-next.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.31-next.1 - - @backstage/plugin-devtools-backend@0.3.0-next.1 - - @backstage/plugin-jenkins-backend@0.4.0-next.1 - - @backstage/plugin-lighthouse-backend@0.4.6-next.1 - - @backstage/plugin-linguist-backend@0.5.11-next.1 - - @backstage/plugin-nomad-backend@0.1.16-next.1 - - @backstage/plugin-permission-backend@0.5.37-next.1 - - @backstage/plugin-permission-common@0.7.13-next.1 - - @backstage/plugin-permission-node@0.7.25-next.1 - - @backstage/plugin-playlist-backend@0.3.18-next.1 - - @backstage/plugin-proxy-backend@0.4.12-next.1 - - @backstage/plugin-search-backend@1.5.4-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.18-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.18-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.18-next.1 - - @backstage/plugin-search-backend-node@1.2.18-next.1 - - @backstage/plugin-sonarqube-backend@0.2.16-next.1 - - @backstage/plugin-techdocs-backend@1.9.7-next.1 - - @backstage/plugin-todo-backend@0.3.12-next.1 - - @backstage/backend-defaults@0.2.14-next.1 - - @backstage/catalog-model@1.4.5-next.0 - - @backstage/plugin-auth-backend-module-github-provider@0.1.11-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.11-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.11-next.1 - -## 0.0.21-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-linguist-backend@0.5.10-next.0 - - @backstage/plugin-auth-node@0.4.8-next.0 - - @backstage/plugin-lighthouse-backend@0.4.5-next.0 - - @backstage/plugin-auth-backend-module-guest-provider@0.1.0-next.0 - - @backstage/plugin-playlist-backend@0.3.17-next.0 - - @backstage/backend-plugin-api@0.6.13-next.0 - - @backstage/plugin-entity-feedback-backend@0.2.10-next.0 - - @backstage/plugin-notifications-backend@0.1.0-next.0 - - @backstage/plugin-catalog-backend@1.18.0-next.0 - - @backstage/plugin-auth-backend@0.22.0-next.0 - - @backstage/plugin-jenkins-backend@0.4.0-next.0 - - @backstage/plugin-azure-devops-backend@0.6.0-next.0 - - @backstage/plugin-scaffolder-backend-module-github@0.2.3-next.0 - - @backstage/plugin-scaffolder-backend@1.22.0-next.0 - - @backstage/plugin-permission-common@0.7.13-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.17-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.17-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.17-next.0 - - @backstage/backend-defaults@0.2.13-next.0 - - @backstage/plugin-kubernetes-backend@0.16.0-next.0 - - @backstage/plugin-adr-backend@0.4.10-next.0 - - @backstage/plugin-proxy-backend@0.4.11-next.0 - - @backstage/backend-tasks@0.5.18-next.0 - - @backstage/plugin-search-backend-node@1.2.17-next.0 - - @backstage/plugin-signals-backend@0.0.4-next.0 - - @backstage/plugin-search-backend@1.5.3-next.0 - - @backstage/plugin-devtools-backend@0.3.0-next.0 - - @backstage/plugin-permission-node@0.7.24-next.0 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.6-next.0 - - @backstage/plugin-badges-backend@0.3.10-next.0 - - @backstage/plugin-permission-backend@0.5.36-next.0 - - @backstage/plugin-app-backend@0.3.61-next.0 - - @backstage/plugin-auth-backend-module-github-provider@0.1.10-next.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.30-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.10-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.10-next.0 - - @backstage/plugin-sonarqube-backend@0.2.15-next.0 - - @backstage/plugin-techdocs-backend@1.9.6-next.0 - - @backstage/plugin-nomad-backend@0.1.15-next.0 - - @backstage/plugin-todo-backend@0.3.11-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.10-next.0 - - @backstage/catalog-model@1.4.5-next.0 - -## 0.0.20 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7 - - @backstage/plugin-scaffolder-backend@1.21.0 - - @backstage/plugin-badges-backend@0.3.7 - - @backstage/plugin-azure-devops-backend@0.5.2 - - @backstage/plugin-auth-node@0.4.4 - - @backstage/plugin-entity-feedback-backend@0.2.7 - - @backstage/plugin-lighthouse-backend@0.4.2 - - @backstage/plugin-devtools-backend@0.2.7 - - @backstage/plugin-linguist-backend@0.5.7 - - @backstage/plugin-adr-backend@0.4.7 - - @backstage/plugin-kubernetes-backend@0.15.0 - - @backstage/plugin-signals-backend@0.0.1 - - @backstage/plugin-notifications-backend@0.0.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.27 - - @backstage/plugin-search-backend-module-techdocs@0.1.14 - - @backstage/plugin-search-backend-module-catalog@0.1.14 - - @backstage/plugin-search-backend-module-explore@0.1.14 - - @backstage/backend-plugin-api@0.6.10 - - @backstage/backend-defaults@0.2.10 - - @backstage/plugin-sonarqube-backend@0.2.12 - - @backstage/plugin-playlist-backend@0.3.14 - - @backstage/plugin-catalog-backend@1.17.0 - - @backstage/plugin-jenkins-backend@0.3.4 - - @backstage/backend-tasks@0.5.15 - - @backstage/plugin-nomad-backend@0.1.12 - - @backstage/plugin-app-backend@0.3.58 - - @backstage/plugin-search-backend@1.5.0 - - @backstage/plugin-todo-backend@0.3.8 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3 - - @backstage/plugin-techdocs-backend@1.9.3 - - @backstage/plugin-permission-backend@0.5.33 - - @backstage/plugin-permission-node@0.7.21 - - @backstage/plugin-proxy-backend@0.4.8 - - @backstage/plugin-search-backend-node@1.2.14 - - @backstage/plugin-permission-common@0.7.12 - -## 0.0.20-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-badges-backend@0.3.7-next.3 - - @backstage/plugin-kubernetes-backend@0.15.0-next.3 - - @backstage/backend-tasks@0.5.15-next.3 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.3 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.3 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.3 - - @backstage/plugin-notifications-backend@0.0.1-next.1 - - @backstage/plugin-signals-backend@0.0.1-next.3 - - @backstage/plugin-catalog-backend@1.17.0-next.3 - - @backstage/plugin-app-backend@0.3.58-next.3 - - @backstage/backend-defaults@0.2.10-next.3 - - @backstage/plugin-adr-backend@0.4.7-next.3 - - @backstage/plugin-auth-node@0.4.4-next.3 - - @backstage/plugin-azure-devops-backend@0.5.2-next.3 - - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.3 - - @backstage/plugin-devtools-backend@0.2.7-next.3 - - @backstage/plugin-entity-feedback-backend@0.2.7-next.3 - - @backstage/plugin-jenkins-backend@0.3.4-next.3 - - @backstage/plugin-lighthouse-backend@0.4.2-next.3 - - @backstage/plugin-linguist-backend@0.5.7-next.3 - - @backstage/plugin-nomad-backend@0.1.12-next.3 - - @backstage/plugin-permission-backend@0.5.33-next.3 - - @backstage/plugin-permission-node@0.7.21-next.3 - - @backstage/plugin-playlist-backend@0.3.14-next.3 - - @backstage/plugin-proxy-backend@0.4.8-next.3 - - @backstage/plugin-scaffolder-backend@1.21.0-next.3 - - @backstage/plugin-search-backend@1.5.0-next.3 - - @backstage/plugin-search-backend-module-catalog@0.1.14-next.3 - - @backstage/plugin-search-backend-module-explore@0.1.14-next.3 - - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.3 - - @backstage/plugin-search-backend-node@1.2.14-next.3 - - @backstage/plugin-sonarqube-backend@0.2.12-next.3 - - @backstage/plugin-techdocs-backend@1.9.3-next.3 - - @backstage/plugin-todo-backend@0.3.8-next.3 - - @backstage/backend-plugin-api@0.6.10-next.3 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.3 - - @backstage/plugin-permission-common@0.7.12 - -## 0.0.20-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.21.0-next.2 - - @backstage/plugin-signals-backend@0.0.1-next.2 - - @backstage/plugin-kubernetes-backend@0.15.0-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.2 - - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.14-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.14-next.2 - - @backstage/plugin-entity-feedback-backend@0.2.7-next.2 - - @backstage/plugin-azure-devops-backend@0.5.2-next.2 - - @backstage/backend-plugin-api@0.6.10-next.2 - - @backstage/plugin-lighthouse-backend@0.4.2-next.2 - - @backstage/backend-defaults@0.2.10-next.2 - - @backstage/plugin-sonarqube-backend@0.2.12-next.2 - - @backstage/plugin-devtools-backend@0.2.7-next.2 - - @backstage/plugin-linguist-backend@0.5.7-next.2 - - @backstage/plugin-playlist-backend@0.3.14-next.2 - - @backstage/plugin-catalog-backend@1.17.0-next.2 - - @backstage/plugin-jenkins-backend@0.3.4-next.2 - - @backstage/backend-tasks@0.5.15-next.2 - - @backstage/plugin-badges-backend@0.3.7-next.2 - - @backstage/plugin-nomad-backend@0.1.12-next.2 - - @backstage/plugin-adr-backend@0.4.7-next.2 - - @backstage/plugin-app-backend@0.3.58-next.2 - - @backstage/plugin-auth-node@0.4.4-next.2 - - @backstage/plugin-notifications-backend@0.0.1-next.0 - - @backstage/plugin-todo-backend@0.3.8-next.2 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.2 - - @backstage/plugin-permission-backend@0.5.33-next.2 - - @backstage/plugin-permission-node@0.7.21-next.2 - - @backstage/plugin-proxy-backend@0.4.8-next.2 - - @backstage/plugin-search-backend@1.5.0-next.2 - - @backstage/plugin-search-backend-node@1.2.14-next.2 - - @backstage/plugin-techdocs-backend@1.9.3-next.2 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.2 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.2 - - @backstage/plugin-permission-common@0.7.12 - -## 0.0.20-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.21.0-next.1 - - @backstage/plugin-azure-devops-backend@0.5.2-next.1 - - @backstage/plugin-catalog-backend@1.17.0-next.1 - - @backstage/backend-plugin-api@0.6.10-next.1 - - @backstage/backend-defaults@0.2.10-next.1 - - @backstage/backend-tasks@0.5.15-next.1 - - @backstage/plugin-adr-backend@0.4.7-next.1 - - @backstage/plugin-app-backend@0.3.58-next.1 - - @backstage/plugin-auth-node@0.4.4-next.1 - - @backstage/plugin-badges-backend@0.3.7-next.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.1 - - @backstage/plugin-devtools-backend@0.2.7-next.1 - - @backstage/plugin-entity-feedback-backend@0.2.7-next.1 - - @backstage/plugin-jenkins-backend@0.3.4-next.1 - - @backstage/plugin-kubernetes-backend@0.14.2-next.1 - - @backstage/plugin-lighthouse-backend@0.4.2-next.1 - - @backstage/plugin-linguist-backend@0.5.7-next.1 - - @backstage/plugin-nomad-backend@0.1.12-next.1 - - @backstage/plugin-permission-backend@0.5.33-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.1 - - @backstage/plugin-permission-common@0.7.12 - - @backstage/plugin-permission-node@0.7.21-next.1 - - @backstage/plugin-playlist-backend@0.3.14-next.1 - - @backstage/plugin-proxy-backend@0.4.8-next.1 - - @backstage/plugin-search-backend@1.5.0-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.14-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.14-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.1 - - @backstage/plugin-search-backend-node@1.2.14-next.1 - - @backstage/plugin-sonarqube-backend@0.2.12-next.1 - - @backstage/plugin-techdocs-backend@1.9.3-next.1 - - @backstage/plugin-todo-backend@0.3.8-next.1 - -## 0.0.20-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-azure-devops-backend@0.5.2-next.0 - - @backstage/plugin-kubernetes-backend@0.14.2-next.0 - - @backstage/plugin-catalog-backend@1.17.0-next.0 - - @backstage/plugin-search-backend@1.5.0-next.0 - - @backstage/plugin-todo-backend@0.3.8-next.0 - - @backstage/plugin-scaffolder-backend@1.21.0-next.0 - - @backstage/plugin-app-backend@0.3.58-next.0 - - @backstage/backend-defaults@0.2.10-next.0 - - @backstage/backend-tasks@0.5.15-next.0 - - @backstage/plugin-auth-node@0.4.4-next.0 - - @backstage/plugin-badges-backend@0.3.7-next.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 - - @backstage/plugin-entity-feedback-backend@0.2.7-next.0 - - @backstage/plugin-linguist-backend@0.5.7-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.0 - - @backstage/plugin-permission-node@0.7.21-next.0 - - @backstage/plugin-playlist-backend@0.3.14-next.0 - - @backstage/plugin-proxy-backend@0.4.8-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 - - @backstage/plugin-sonarqube-backend@0.2.12-next.0 - - @backstage/plugin-techdocs-backend@1.9.3-next.0 - - @backstage/plugin-adr-backend@0.4.7-next.0 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.0 - - @backstage/plugin-devtools-backend@0.2.7-next.0 - - @backstage/plugin-jenkins-backend@0.3.4-next.0 - - @backstage/plugin-lighthouse-backend@0.4.2-next.0 - - @backstage/plugin-nomad-backend@0.1.12-next.0 - - @backstage/plugin-permission-backend@0.5.33-next.0 - - @backstage/plugin-search-backend-node@1.2.14-next.0 - - @backstage/backend-plugin-api@0.6.10-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 - - @backstage/plugin-permission-common@0.7.12 - -## 0.0.19 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-sonarqube-backend@0.2.11 - - @backstage/plugin-scaffolder-backend@1.20.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.26 - - @backstage/plugin-search-backend-module-techdocs@0.1.13 - - @backstage/plugin-search-backend-module-catalog@0.1.13 - - @backstage/plugin-search-backend-module-explore@0.1.13 - - @backstage/backend-plugin-api@0.6.9 - - @backstage/backend-defaults@0.2.9 - - @backstage/plugin-azure-devops-backend@0.5.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2 - - @backstage/plugin-entity-feedback-backend@0.2.6 - - @backstage/plugin-devtools-backend@0.2.6 - - @backstage/plugin-linguist-backend@0.5.6 - - @backstage/plugin-playlist-backend@0.3.13 - - @backstage/plugin-techdocs-backend@1.9.2 - - @backstage/plugin-jenkins-backend@0.3.3 - - @backstage/plugin-badges-backend@0.3.6 - - @backstage/plugin-search-backend@1.4.9 - - @backstage/plugin-nomad-backend@0.1.11 - - @backstage/plugin-todo-backend@0.3.7 - - @backstage/plugin-adr-backend@0.4.6 - - @backstage/plugin-app-backend@0.3.57 - - @backstage/plugin-permission-backend@0.5.32 - - @backstage/plugin-permission-common@0.7.12 - - @backstage/plugin-permission-node@0.7.20 - - @backstage/plugin-catalog-backend@1.16.1 - - @backstage/backend-tasks@0.5.14 - - @backstage/plugin-auth-node@0.4.3 - - @backstage/plugin-kubernetes-backend@0.14.1 - - @backstage/plugin-lighthouse-backend@0.4.1 - - @backstage/plugin-proxy-backend@0.4.7 - - @backstage/plugin-search-backend-node@1.2.13 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 - -## 0.0.19-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-sonarqube-backend@0.2.11-next.2 - - @backstage/backend-plugin-api@0.6.9-next.2 - - @backstage/backend-defaults@0.2.9-next.2 - - @backstage/plugin-adr-backend@0.4.6-next.2 - - @backstage/plugin-app-backend@0.3.57-next.2 - - @backstage/plugin-auth-node@0.4.3-next.2 - - @backstage/plugin-azure-devops-backend@0.5.1-next.2 - - @backstage/plugin-badges-backend@0.3.6-next.2 - - @backstage/plugin-catalog-backend@1.16.1-next.2 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.2 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 - - @backstage/plugin-devtools-backend@0.2.6-next.2 - - @backstage/plugin-entity-feedback-backend@0.2.6-next.2 - - @backstage/plugin-jenkins-backend@0.3.3-next.2 - - @backstage/plugin-kubernetes-backend@0.14.1-next.2 - - @backstage/plugin-lighthouse-backend@0.4.1-next.2 - - @backstage/plugin-linguist-backend@0.5.6-next.2 - - @backstage/plugin-nomad-backend@0.1.11-next.2 - - @backstage/plugin-permission-backend@0.5.32-next.2 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.2 - - @backstage/plugin-permission-node@0.7.20-next.2 - - @backstage/plugin-playlist-backend@0.3.13-next.2 - - @backstage/plugin-proxy-backend@0.4.7-next.2 - - @backstage/plugin-scaffolder-backend@1.19.3-next.2 - - @backstage/plugin-search-backend@1.4.9-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 - - @backstage/plugin-search-backend-node@1.2.13-next.2 - - @backstage/plugin-techdocs-backend@1.9.2-next.2 - - @backstage/plugin-todo-backend@0.3.7-next.2 - - @backstage/backend-tasks@0.5.14-next.2 - - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.2 - -## 0.0.19-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-app-backend@0.3.57-next.1 - - @backstage/plugin-devtools-backend@0.2.6-next.1 - - @backstage/plugin-proxy-backend@0.4.7-next.1 - - @backstage/backend-defaults@0.2.9-next.1 - - @backstage/plugin-kubernetes-backend@0.14.1-next.1 - - @backstage/backend-tasks@0.5.14-next.1 - - @backstage/plugin-adr-backend@0.4.6-next.1 - - @backstage/plugin-auth-node@0.4.3-next.1 - - @backstage/plugin-azure-devops-backend@0.5.1-next.1 - - @backstage/plugin-badges-backend@0.3.6-next.1 - - @backstage/plugin-catalog-backend@1.16.1-next.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.1 - - @backstage/plugin-entity-feedback-backend@0.2.6-next.1 - - @backstage/plugin-jenkins-backend@0.3.3-next.1 - - @backstage/plugin-lighthouse-backend@0.4.1-next.1 - - @backstage/plugin-linguist-backend@0.5.6-next.1 - - @backstage/plugin-nomad-backend@0.1.11-next.1 - - @backstage/plugin-permission-backend@0.5.32-next.1 - - @backstage/plugin-permission-node@0.7.20-next.1 - - @backstage/plugin-playlist-backend@0.3.13-next.1 - - @backstage/plugin-scaffolder-backend@1.19.3-next.1 - - @backstage/plugin-search-backend@1.4.9-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 - - @backstage/plugin-search-backend-node@1.2.13-next.1 - - @backstage/plugin-sonarqube-backend@0.2.11-next.1 - - @backstage/plugin-techdocs-backend@1.9.2-next.1 - - @backstage/plugin-todo-backend@0.3.7-next.1 - - @backstage/backend-plugin-api@0.6.9-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.1 - - @backstage/plugin-permission-common@0.7.11 - -## 0.0.19-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.19.3-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 - - @backstage/plugin-azure-devops-backend@0.5.1-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.0 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.0 - - @backstage/plugin-entity-feedback-backend@0.2.6-next.0 - - @backstage/plugin-devtools-backend@0.2.6-next.0 - - @backstage/plugin-linguist-backend@0.5.6-next.0 - - @backstage/plugin-playlist-backend@0.3.13-next.0 - - @backstage/plugin-techdocs-backend@1.9.2-next.0 - - @backstage/plugin-jenkins-backend@0.3.3-next.0 - - @backstage/plugin-badges-backend@0.3.6-next.0 - - @backstage/plugin-search-backend@1.4.9-next.0 - - @backstage/plugin-nomad-backend@0.1.11-next.0 - - @backstage/plugin-todo-backend@0.3.7-next.0 - - @backstage/plugin-adr-backend@0.4.6-next.0 - - @backstage/plugin-app-backend@0.3.57-next.0 - - @backstage/backend-defaults@0.2.9-next.0 - - @backstage/backend-plugin-api@0.6.9-next.0 - - @backstage/backend-tasks@0.5.14-next.0 - - @backstage/plugin-auth-node@0.4.3-next.0 - - @backstage/plugin-catalog-backend@1.16.1-next.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 - - @backstage/plugin-kubernetes-backend@0.14.1-next.0 - - @backstage/plugin-lighthouse-backend@0.4.1-next.0 - - @backstage/plugin-permission-backend@0.5.32-next.0 - - @backstage/plugin-permission-common@0.7.11 - - @backstage/plugin-permission-node@0.7.20-next.0 - - @backstage/plugin-proxy-backend@0.4.7-next.0 - - @backstage/plugin-search-backend-node@1.2.13-next.0 - - @backstage/plugin-sonarqube-backend@0.2.11-next.0 - -## 0.0.18 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1 - - @backstage/plugin-techdocs-backend@1.9.1 - - @backstage/plugin-catalog-backend@1.16.0 - - @backstage/plugin-azure-devops-backend@0.5.0 - - @backstage/plugin-scaffolder-backend@1.19.2 - - @backstage/backend-tasks@0.5.13 - - @backstage/plugin-lighthouse-backend@0.4.0 - - @backstage/plugin-kubernetes-backend@0.14.0 - - @backstage/plugin-auth-node@0.4.2 - - @backstage/plugin-permission-backend@0.5.31 - - @backstage/plugin-permission-common@0.7.11 - - @backstage/plugin-playlist-backend@0.3.12 - - @backstage/plugin-permission-node@0.7.19 - - @backstage/plugin-search-backend@1.4.8 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 - - @backstage/plugin-search-backend-module-techdocs@0.1.12 - - @backstage/plugin-search-backend-module-catalog@0.1.12 - - @backstage/plugin-search-backend-module-explore@0.1.12 - - @backstage/backend-defaults@0.2.8 - - @backstage/plugin-adr-backend@0.4.5 - - @backstage/plugin-app-backend@0.3.56 - - @backstage/plugin-badges-backend@0.3.5 - - @backstage/plugin-catalog-backend-module-openapi@0.1.25 - - @backstage/plugin-devtools-backend@0.2.5 - - @backstage/plugin-entity-feedback-backend@0.2.5 - - @backstage/plugin-jenkins-backend@0.3.2 - - @backstage/plugin-linguist-backend@0.5.5 - - @backstage/plugin-nomad-backend@0.1.10 - - @backstage/plugin-proxy-backend@0.4.6 - - @backstage/plugin-search-backend-node@1.2.12 - - @backstage/plugin-sonarqube-backend@0.2.10 - - @backstage/plugin-todo-backend@0.3.6 - - @backstage/backend-plugin-api@0.6.8 - -## 0.0.18-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-azure-devops-backend@0.5.0-next.3 - - @backstage/plugin-scaffolder-backend@1.19.2-next.3 - - @backstage/backend-defaults@0.2.8-next.3 - - @backstage/backend-plugin-api@0.6.8-next.3 - - @backstage/backend-tasks@0.5.13-next.3 - - @backstage/plugin-adr-backend@0.4.5-next.3 - - @backstage/plugin-app-backend@0.3.56-next.3 - - @backstage/plugin-auth-node@0.4.2-next.3 - - @backstage/plugin-badges-backend@0.3.5-next.3 - - @backstage/plugin-catalog-backend@1.16.0-next.3 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.3 - - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.3 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 - - @backstage/plugin-devtools-backend@0.2.5-next.3 - - @backstage/plugin-entity-feedback-backend@0.2.5-next.3 - - @backstage/plugin-jenkins-backend@0.3.2-next.3 - - @backstage/plugin-kubernetes-backend@0.14.0-next.3 - - @backstage/plugin-lighthouse-backend@0.4.0-next.3 - - @backstage/plugin-linguist-backend@0.5.5-next.3 - - @backstage/plugin-nomad-backend@0.1.10-next.3 - - @backstage/plugin-permission-backend@0.5.31-next.3 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.3 - - @backstage/plugin-permission-common@0.7.10 - - @backstage/plugin-permission-node@0.7.19-next.3 - - @backstage/plugin-playlist-backend@0.3.12-next.3 - - @backstage/plugin-proxy-backend@0.4.6-next.3 - - @backstage/plugin-search-backend@1.4.8-next.3 - - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 - - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 - - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 - - @backstage/plugin-search-backend-node@1.2.12-next.3 - - @backstage/plugin-sonarqube-backend@0.2.10-next.3 - - @backstage/plugin-techdocs-backend@1.9.1-next.3 - - @backstage/plugin-todo-backend@0.3.6-next.3 - -## 0.0.18-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.16.0-next.2 - - @backstage/plugin-lighthouse-backend@0.4.0-next.2 - - @backstage/plugin-auth-node@0.4.2-next.2 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 - - @backstage/backend-defaults@0.2.8-next.2 - - @backstage/backend-plugin-api@0.6.8-next.2 - - @backstage/backend-tasks@0.5.13-next.2 - - @backstage/plugin-adr-backend@0.4.5-next.2 - - @backstage/plugin-app-backend@0.3.56-next.2 - - @backstage/plugin-azure-devops-backend@0.5.0-next.2 - - @backstage/plugin-badges-backend@0.3.5-next.2 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.2 - - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.2 - - @backstage/plugin-devtools-backend@0.2.5-next.2 - - @backstage/plugin-entity-feedback-backend@0.2.5-next.2 - - @backstage/plugin-jenkins-backend@0.3.2-next.2 - - @backstage/plugin-kubernetes-backend@0.14.0-next.2 - - @backstage/plugin-linguist-backend@0.5.5-next.2 - - @backstage/plugin-nomad-backend@0.1.10-next.2 - - @backstage/plugin-permission-backend@0.5.31-next.2 - - @backstage/plugin-permission-common@0.7.10 - - @backstage/plugin-permission-node@0.7.19-next.2 - - @backstage/plugin-playlist-backend@0.3.12-next.2 - - @backstage/plugin-proxy-backend@0.4.6-next.2 - - @backstage/plugin-scaffolder-backend@1.19.2-next.2 - - @backstage/plugin-search-backend@1.4.8-next.2 - - @backstage/plugin-search-backend-node@1.2.12-next.2 - - @backstage/plugin-sonarqube-backend@0.2.10-next.2 - - @backstage/plugin-techdocs-backend@1.9.1-next.2 - - @backstage/plugin-todo-backend@0.3.6-next.2 - -## 0.0.18-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.1 - - @backstage/plugin-catalog-backend@1.15.1-next.1 - - @backstage/plugin-azure-devops-backend@0.5.0-next.1 - - @backstage/plugin-kubernetes-backend@0.14.0-next.1 - - @backstage/backend-defaults@0.2.8-next.1 - - @backstage/backend-plugin-api@0.6.8-next.1 - - @backstage/backend-tasks@0.5.13-next.1 - - @backstage/plugin-adr-backend@0.4.5-next.1 - - @backstage/plugin-app-backend@0.3.56-next.1 - - @backstage/plugin-auth-node@0.4.2-next.1 - - @backstage/plugin-badges-backend@0.3.5-next.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 - - @backstage/plugin-devtools-backend@0.2.5-next.1 - - @backstage/plugin-entity-feedback-backend@0.2.5-next.1 - - @backstage/plugin-jenkins-backend@0.3.2-next.1 - - @backstage/plugin-lighthouse-backend@0.3.5-next.1 - - @backstage/plugin-linguist-backend@0.5.5-next.1 - - @backstage/plugin-nomad-backend@0.1.10-next.1 - - @backstage/plugin-permission-backend@0.5.31-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.1 - - @backstage/plugin-permission-common@0.7.10 - - @backstage/plugin-permission-node@0.7.19-next.1 - - @backstage/plugin-playlist-backend@0.3.12-next.1 - - @backstage/plugin-proxy-backend@0.4.6-next.1 - - @backstage/plugin-scaffolder-backend@1.19.2-next.1 - - @backstage/plugin-search-backend@1.4.8-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 - - @backstage/plugin-search-backend-node@1.2.12-next.1 - - @backstage/plugin-sonarqube-backend@0.2.10-next.1 - - @backstage/plugin-techdocs-backend@1.9.1-next.1 - - @backstage/plugin-todo-backend@0.3.6-next.1 - -## 0.0.18-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-tasks@0.5.13-next.0 - - @backstage/plugin-scaffolder-backend@1.19.2-next.0 - - @backstage/plugin-kubernetes-backend@0.14.0-next.0 - - @backstage/backend-defaults@0.2.8-next.0 - - @backstage/plugin-adr-backend@0.4.5-next.0 - - @backstage/plugin-app-backend@0.3.56-next.0 - - @backstage/plugin-auth-node@0.4.2-next.0 - - @backstage/plugin-azure-devops-backend@0.4.5-next.0 - - @backstage/plugin-badges-backend@0.3.5-next.0 - - @backstage/plugin-catalog-backend@1.15.1-next.0 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.0 - - @backstage/plugin-devtools-backend@0.2.5-next.0 - - @backstage/plugin-entity-feedback-backend@0.2.5-next.0 - - @backstage/plugin-jenkins-backend@0.3.2-next.0 - - @backstage/plugin-lighthouse-backend@0.3.5-next.0 - - @backstage/plugin-linguist-backend@0.5.5-next.0 - - @backstage/plugin-nomad-backend@0.1.10-next.0 - - @backstage/plugin-permission-backend@0.5.31-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.0 - - @backstage/plugin-permission-node@0.7.19-next.0 - - @backstage/plugin-playlist-backend@0.3.12-next.0 - - @backstage/plugin-proxy-backend@0.4.6-next.0 - - @backstage/plugin-search-backend@1.4.8-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.12-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.12-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.0 - - @backstage/plugin-search-backend-node@1.2.12-next.0 - - @backstage/plugin-sonarqube-backend@0.2.10-next.0 - - @backstage/plugin-techdocs-backend@1.9.1-next.0 - - @backstage/plugin-todo-backend@0.3.6-next.0 - - @backstage/backend-plugin-api@0.6.8-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.0 - - @backstage/plugin-permission-common@0.7.10 - -## 0.0.17 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.15.0 - - @backstage/plugin-kubernetes-backend@0.13.1 - - @backstage/plugin-search-backend-node@1.2.11 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0 - - @backstage/plugin-techdocs-backend@1.9.0 - - @backstage/plugin-scaffolder-backend@1.19.0 - - @backstage/plugin-search-backend@1.4.7 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4 - - @backstage/plugin-entity-feedback-backend@0.2.4 - - @backstage/backend-plugin-api@0.6.7 - - @backstage/plugin-linguist-backend@0.5.4 - - @backstage/plugin-playlist-backend@0.3.11 - - @backstage/backend-tasks@0.5.12 - - @backstage/plugin-badges-backend@0.3.4 - - @backstage/plugin-app-backend@0.3.55 - - @backstage/plugin-search-backend-module-techdocs@0.1.11 - - @backstage/plugin-permission-common@0.7.10 - - @backstage/plugin-jenkins-backend@0.3.1 - - @backstage/plugin-adr-backend@0.4.4 - - @backstage/plugin-proxy-backend@0.4.5 - - @backstage/plugin-catalog-backend-module-openapi@0.1.24 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4 - - @backstage/plugin-lighthouse-backend@0.3.4 - - @backstage/plugin-search-backend-module-catalog@0.1.11 - - @backstage/plugin-todo-backend@0.3.5 - - @backstage/plugin-devtools-backend@0.2.4 - - @backstage/backend-defaults@0.2.7 - - @backstage/plugin-auth-node@0.4.1 - - @backstage/plugin-azure-devops-backend@0.4.4 - - @backstage/plugin-nomad-backend@0.1.9 - - @backstage/plugin-permission-backend@0.5.30 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4 - - @backstage/plugin-permission-node@0.7.18 - - @backstage/plugin-search-backend-module-explore@0.1.11 - - @backstage/plugin-sonarqube-backend@0.2.9 - -## 0.0.17-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.13.1-next.2 - - @backstage/plugin-scaffolder-backend@1.19.0-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.2 - - @backstage/plugin-entity-feedback-backend@0.2.4-next.2 - - @backstage/backend-plugin-api@0.6.7-next.2 - - @backstage/plugin-linguist-backend@0.5.4-next.2 - - @backstage/plugin-playlist-backend@0.3.11-next.2 - - @backstage/plugin-techdocs-backend@1.9.0-next.2 - - @backstage/plugin-catalog-backend@1.15.0-next.2 - - @backstage/backend-tasks@0.5.12-next.2 - - @backstage/plugin-badges-backend@0.3.4-next.2 - - @backstage/plugin-app-backend@0.3.55-next.2 - - @backstage/backend-defaults@0.2.7-next.2 - - @backstage/plugin-adr-backend@0.4.4-next.2 - - @backstage/plugin-auth-node@0.4.1-next.2 - - @backstage/plugin-azure-devops-backend@0.4.4-next.2 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0-next.2 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.2 - - @backstage/plugin-devtools-backend@0.2.4-next.2 - - @backstage/plugin-jenkins-backend@0.3.1-next.2 - - @backstage/plugin-lighthouse-backend@0.3.4-next.2 - - @backstage/plugin-nomad-backend@0.1.9-next.2 - - @backstage/plugin-permission-backend@0.5.30-next.2 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4-next.2 - - @backstage/plugin-permission-node@0.7.18-next.2 - - @backstage/plugin-proxy-backend@0.4.5-next.2 - - @backstage/plugin-search-backend@1.4.7-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.11-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.11-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.2 - - @backstage/plugin-search-backend-node@1.2.11-next.2 - - @backstage/plugin-sonarqube-backend@0.2.9-next.2 - - @backstage/plugin-todo-backend@0.3.5-next.2 - - @backstage/plugin-catalog-backend-module-openapi@0.1.24-next.2 - -## 0.0.17-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.15.0-next.1 - - @backstage/plugin-techdocs-backend@1.9.0-next.1 - - @backstage/plugin-scaffolder-backend@1.19.0-next.1 - - @backstage/plugin-catalog-backend-module-openapi@0.1.24-next.1 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.1 - - @backstage/plugin-jenkins-backend@0.3.1-next.1 - - @backstage/plugin-kubernetes-backend@0.13.1-next.1 - - @backstage/plugin-lighthouse-backend@0.3.4-next.1 - - @backstage/plugin-linguist-backend@0.5.4-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.11-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.1 - - @backstage/plugin-todo-backend@0.3.5-next.1 - - @backstage/plugin-adr-backend@0.4.4-next.1 - - @backstage/backend-defaults@0.2.7-next.1 - - @backstage/backend-tasks@0.5.12-next.1 - - @backstage/plugin-app-backend@0.3.55-next.1 - - @backstage/plugin-auth-node@0.4.1-next.1 - - @backstage/plugin-badges-backend@0.3.4-next.1 - - @backstage/plugin-entity-feedback-backend@0.2.4-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4-next.1 - - @backstage/plugin-permission-node@0.7.18-next.1 - - @backstage/plugin-playlist-backend@0.3.11-next.1 - - @backstage/plugin-proxy-backend@0.4.5-next.1 - - @backstage/plugin-search-backend@1.4.7-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.11-next.1 - - @backstage/plugin-sonarqube-backend@0.2.9-next.1 - - @backstage/plugin-azure-devops-backend@0.4.4-next.1 - - @backstage/plugin-devtools-backend@0.2.4-next.1 - - @backstage/plugin-nomad-backend@0.1.9-next.1 - - @backstage/plugin-permission-backend@0.5.30-next.1 - - @backstage/plugin-search-backend-node@1.2.11-next.1 - - @backstage/backend-plugin-api@0.6.7-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.1 - - @backstage/plugin-permission-common@0.7.9 - -## 0.0.17-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-search-backend-node@1.2.11-next.0 - - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0-next.0 - - @backstage/plugin-techdocs-backend@1.8.1-next.0 - - @backstage/plugin-scaffolder-backend@1.19.0-next.0 - - @backstage/plugin-catalog-backend@1.15.0-next.0 - - @backstage/plugin-search-backend@1.4.7-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.0 - - @backstage/plugin-proxy-backend@0.4.5-next.0 - - @backstage/plugin-app-backend@0.3.55-next.0 - - @backstage/plugin-devtools-backend@0.2.4-next.0 - - @backstage/backend-defaults@0.2.7-next.0 - - @backstage/backend-plugin-api@0.6.7-next.0 - - @backstage/backend-tasks@0.5.12-next.0 - - @backstage/plugin-adr-backend@0.4.4-next.0 - - @backstage/plugin-auth-node@0.4.1-next.0 - - @backstage/plugin-azure-devops-backend@0.4.4-next.0 - - @backstage/plugin-badges-backend@0.3.4-next.0 - - @backstage/plugin-catalog-backend-module-openapi@0.1.24-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.0 - - @backstage/plugin-entity-feedback-backend@0.2.4-next.0 - - @backstage/plugin-jenkins-backend@0.3.1-next.0 - - @backstage/plugin-kubernetes-backend@0.13.1-next.0 - - @backstage/plugin-lighthouse-backend@0.3.4-next.0 - - @backstage/plugin-linguist-backend@0.5.4-next.0 - - @backstage/plugin-nomad-backend@0.1.9-next.0 - - @backstage/plugin-permission-backend@0.5.30-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4-next.0 - - @backstage/plugin-permission-common@0.7.9 - - @backstage/plugin-permission-node@0.7.18-next.0 - - @backstage/plugin-playlist-backend@0.3.11-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.11-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.11-next.0 - - @backstage/plugin-sonarqube-backend@0.2.9-next.0 - - @backstage/plugin-todo-backend@0.3.5-next.0 - -## 0.0.16 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-nomad-backend@0.1.8 - - @backstage/backend-tasks@0.5.11 - - @backstage/plugin-sonarqube-backend@0.2.8 - - @backstage/plugin-scaffolder-backend@1.18.0 - - @backstage/plugin-playlist-backend@0.3.10 - - @backstage/plugin-techdocs-backend@1.8.0 - - @backstage/plugin-catalog-backend@1.14.0 - - @backstage/plugin-auth-node@0.4.0 - - @backstage/plugin-badges-backend@0.3.3 - - @backstage/plugin-kubernetes-backend@0.13.0 - - @backstage/plugin-jenkins-backend@0.3.0 - - @backstage/plugin-search-backend@1.4.6 - - @backstage/backend-plugin-api@0.6.6 - - @backstage/plugin-lighthouse-backend@0.3.3 - - @backstage/plugin-linguist-backend@0.5.3 - - @backstage/plugin-search-backend-module-catalog@0.1.10 - - @backstage/plugin-search-backend-module-explore@0.1.10 - - @backstage/plugin-search-backend-module-techdocs@0.1.10 - - @backstage/plugin-search-backend-node@1.2.10 - - @backstage/backend-defaults@0.2.6 - - @backstage/plugin-adr-backend@0.4.3 - - @backstage/plugin-app-backend@0.3.54 - - @backstage/plugin-azure-devops-backend@0.4.3 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.3 - - @backstage/plugin-devtools-backend@0.2.3 - - @backstage/plugin-entity-feedback-backend@0.2.3 - - @backstage/plugin-permission-backend@0.5.29 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.3 - - @backstage/plugin-permission-node@0.7.17 - - @backstage/plugin-proxy-backend@0.4.3 - - @backstage/plugin-todo-backend@0.3.4 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.3 - - @backstage/plugin-permission-common@0.7.9 - -## 0.0.16-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-nomad-backend@0.1.8-next.2 - - @backstage/plugin-scaffolder-backend@1.18.0-next.2 - - @backstage/plugin-techdocs-backend@1.8.0-next.2 - - @backstage/plugin-auth-node@0.4.0-next.2 - - @backstage/plugin-catalog-backend@1.14.0-next.2 - - @backstage/plugin-kubernetes-backend@0.12.3-next.2 - - @backstage/plugin-jenkins-backend@0.2.9-next.2 - - @backstage/backend-defaults@0.2.6-next.2 - - @backstage/backend-tasks@0.5.11-next.2 - - @backstage/plugin-adr-backend@0.4.3-next.2 - - @backstage/plugin-app-backend@0.3.54-next.2 - - @backstage/plugin-azure-devops-backend@0.4.3-next.2 - - @backstage/plugin-badges-backend@0.3.3-next.2 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.3-next.2 - - @backstage/plugin-devtools-backend@0.2.3-next.2 - - @backstage/plugin-entity-feedback-backend@0.2.3-next.2 - - @backstage/plugin-lighthouse-backend@0.3.3-next.2 - - @backstage/plugin-linguist-backend@0.5.3-next.2 - - @backstage/plugin-permission-backend@0.5.29-next.2 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.3-next.2 - - @backstage/plugin-permission-node@0.7.17-next.2 - - @backstage/plugin-playlist-backend@0.3.10-next.2 - - @backstage/plugin-proxy-backend@0.4.3-next.2 - - @backstage/plugin-search-backend@1.4.6-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.10-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.10-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.10-next.2 - - @backstage/plugin-search-backend-node@1.2.10-next.2 - - @backstage/plugin-sonarqube-backend@0.2.8-next.2 - - @backstage/plugin-todo-backend@0.3.4-next.2 - - @backstage/backend-plugin-api@0.6.6-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.3-next.2 - - @backstage/plugin-permission-common@0.7.9-next.0 - -## 0.0.16-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-tasks@0.5.10-next.1 - - @backstage/plugin-catalog-backend@1.14.0-next.1 - - @backstage/plugin-scaffolder-backend@1.18.0-next.1 - - @backstage/plugin-badges-backend@0.3.2-next.1 - - @backstage/backend-plugin-api@0.6.5-next.1 - - @backstage/plugin-lighthouse-backend@0.3.2-next.1 - - @backstage/plugin-linguist-backend@0.5.2-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.9-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.9-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.9-next.1 - - @backstage/plugin-search-backend-node@1.2.9-next.1 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.2-next.1 - - @backstage/plugin-kubernetes-backend@0.12.2-next.1 - - @backstage/plugin-todo-backend@0.3.3-next.1 - - @backstage/backend-defaults@0.2.5-next.1 - - @backstage/plugin-adr-backend@0.4.2-next.1 - - @backstage/plugin-app-backend@0.3.53-next.1 - - @backstage/plugin-auth-node@0.3.2-next.1 - - @backstage/plugin-azure-devops-backend@0.4.2-next.1 - - @backstage/plugin-devtools-backend@0.2.2-next.1 - - @backstage/plugin-entity-feedback-backend@0.2.2-next.1 - - @backstage/plugin-permission-backend@0.5.28-next.1 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.2-next.1 - - @backstage/plugin-permission-node@0.7.16-next.1 - - @backstage/plugin-playlist-backend@0.3.9-next.1 - - @backstage/plugin-proxy-backend@0.4.2-next.1 - - @backstage/plugin-search-backend@1.4.5-next.1 - - @backstage/plugin-sonarqube-backend@0.2.7-next.1 - - @backstage/plugin-techdocs-backend@1.7.2-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.2-next.1 - - @backstage/plugin-permission-common@0.7.8 - -## 0.0.16-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-sonarqube-backend@0.2.7-next.0 - - @backstage/plugin-playlist-backend@0.3.9-next.0 - - @backstage/plugin-catalog-backend@1.14.0-next.0 - - @backstage/plugin-auth-node@0.3.2-next.0 - - @backstage/plugin-adr-backend@0.4.2-next.0 - - @backstage/plugin-scaffolder-backend@1.17.3-next.0 - - @backstage/plugin-techdocs-backend@1.7.2-next.0 - - @backstage/plugin-todo-backend@0.3.3-next.0 - - @backstage/backend-defaults@0.2.5-next.0 - - @backstage/backend-plugin-api@0.6.5-next.0 - - @backstage/backend-tasks@0.5.10-next.0 - - @backstage/plugin-app-backend@0.3.53-next.0 - - @backstage/plugin-azure-devops-backend@0.4.2-next.0 - - @backstage/plugin-badges-backend@0.3.2-next.0 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.2-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.2-next.0 - - @backstage/plugin-devtools-backend@0.2.2-next.0 - - @backstage/plugin-entity-feedback-backend@0.2.2-next.0 - - @backstage/plugin-kubernetes-backend@0.12.2-next.0 - - @backstage/plugin-lighthouse-backend@0.3.2-next.0 - - @backstage/plugin-linguist-backend@0.5.2-next.0 - - @backstage/plugin-permission-backend@0.5.28-next.0 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.2-next.0 - - @backstage/plugin-permission-common@0.7.8 - - @backstage/plugin-permission-node@0.7.16-next.0 - - @backstage/plugin-proxy-backend@0.4.2-next.0 - - @backstage/plugin-search-backend@1.4.5-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.9-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.9-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.9-next.0 - - @backstage/plugin-search-backend-node@1.2.9-next.0 - -## 0.0.15 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.13.0 - - @backstage/plugin-kubernetes-backend@0.12.0 - - @backstage/plugin-techdocs-backend@1.7.0 - - @backstage/plugin-proxy-backend@0.4.0 - - @backstage/plugin-adr-backend@0.4.0 - - @backstage/plugin-azure-devops-backend@0.4.0 - - @backstage/plugin-badges-backend@0.3.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.0 - - @backstage/plugin-devtools-backend@0.2.0 - - @backstage/plugin-entity-feedback-backend@0.2.0 - - @backstage/plugin-lighthouse-backend@0.3.0 - - @backstage/plugin-linguist-backend@0.5.0 - - @backstage/plugin-todo-backend@0.3.0 - - @backstage/plugin-app-backend@0.3.51 - - @backstage/plugin-permission-backend@0.5.26 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.0 - - @backstage/plugin-scaffolder-backend@1.17.0 - - @backstage/plugin-search-backend@1.4.3 - - @backstage/plugin-search-backend-module-catalog@0.1.7 - - @backstage/plugin-search-backend-module-explore@0.1.7 - - @backstage/plugin-search-backend-module-techdocs@0.1.7 - - @backstage/backend-tasks@0.5.8 - - @backstage/plugin-auth-node@0.3.0 - - @backstage/plugin-permission-common@0.7.8 - - @backstage/plugin-permission-node@0.7.14 - - @backstage/backend-plugin-api@0.6.3 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.0 - - @backstage/backend-defaults@0.2.3 - - @backstage/plugin-search-backend-node@1.2.7 - -## 0.0.15-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-techdocs-backend@1.7.0-next.3 - - @backstage/plugin-proxy-backend@0.4.0-next.3 - - @backstage/plugin-adr-backend@0.4.0-next.3 - - @backstage/plugin-azure-devops-backend@0.4.0-next.3 - - @backstage/plugin-badges-backend@0.3.0-next.3 - - @backstage/plugin-catalog-backend-module-unprocessed@0.3.0-next.3 - - @backstage/plugin-devtools-backend@0.2.0-next.3 - - @backstage/plugin-entity-feedback-backend@0.2.0-next.3 - - @backstage/plugin-lighthouse-backend@0.3.0-next.3 - - @backstage/plugin-linguist-backend@0.5.0-next.3 - - @backstage/plugin-todo-backend@0.3.0-next.3 - - @backstage/plugin-app-backend@0.3.51-next.3 - - @backstage/plugin-catalog-backend@1.13.0-next.3 - - @backstage/plugin-kubernetes-backend@0.11.6-next.3 - - @backstage/plugin-permission-backend@0.5.26-next.3 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.0-next.1 - - @backstage/plugin-scaffolder-backend@1.17.0-next.3 - - @backstage/plugin-search-backend@1.4.3-next.3 - - @backstage/plugin-search-backend-module-catalog@0.1.7-next.3 - - @backstage/plugin-search-backend-module-explore@0.1.7-next.3 - - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.3 - - @backstage/plugin-permission-common@0.7.8-next.2 - - @backstage/plugin-permission-node@0.7.14-next.3 - - @backstage/backend-plugin-api@0.6.3-next.3 - - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.0-next.0 - - @backstage/backend-defaults@0.2.3-next.3 - - @backstage/backend-tasks@0.5.8-next.3 - - @backstage/plugin-auth-node@0.3.0-next.3 - - @backstage/plugin-search-backend-node@1.2.7-next.3 - -## 0.0.15-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.16.6-next.2 - - @backstage/plugin-permission-backend@0.5.26-next.2 - - @backstage/plugin-catalog-backend@1.13.0-next.2 - - @backstage/plugin-badges-backend@0.2.6-next.2 - - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.0-next.0 - - @backstage/backend-tasks@0.5.8-next.2 - - @backstage/backend-defaults@0.2.3-next.2 - - @backstage/plugin-app-backend@0.3.51-next.2 - - @backstage/plugin-auth-node@0.3.0-next.2 - - @backstage/plugin-entity-feedback-backend@0.1.9-next.2 - - @backstage/plugin-kubernetes-backend@0.11.6-next.2 - - @backstage/plugin-linguist-backend@0.4.3-next.2 - - @backstage/plugin-permission-node@0.7.14-next.2 - - @backstage/plugin-proxy-backend@0.3.3-next.2 - - @backstage/plugin-search-backend@1.4.3-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.7-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.7-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.2 - - @backstage/plugin-techdocs-backend@1.7.0-next.2 - - @backstage/plugin-devtools-backend@0.1.6-next.2 - - @backstage/backend-plugin-api@0.6.3-next.2 - - @backstage/plugin-adr-backend@0.3.9-next.2 - - @backstage/plugin-azure-devops-backend@0.3.30-next.2 - - @backstage/plugin-lighthouse-backend@0.2.7-next.2 - - @backstage/plugin-permission-common@0.7.8-next.1 - - @backstage/plugin-search-backend-node@1.2.7-next.2 - - @backstage/plugin-todo-backend@0.2.3-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.2.3-next.2 - -## 0.0.15-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.11.6-next.1 - - @backstage/plugin-catalog-backend@1.13.0-next.1 - - @backstage/plugin-devtools-backend@0.1.6-next.1 - - @backstage/backend-tasks@0.5.8-next.1 - - @backstage/plugin-techdocs-backend@1.7.0-next.1 - - @backstage/plugin-scaffolder-backend@1.16.6-next.1 - - @backstage/backend-plugin-api@0.6.3-next.1 - - @backstage/plugin-adr-backend@0.3.9-next.1 - - @backstage/plugin-app-backend@0.3.51-next.1 - - @backstage/plugin-auth-node@0.3.0-next.1 - - @backstage/plugin-azure-devops-backend@0.3.30-next.1 - - @backstage/plugin-badges-backend@0.2.6-next.1 - - @backstage/plugin-entity-feedback-backend@0.1.9-next.1 - - @backstage/plugin-lighthouse-backend@0.2.7-next.1 - - @backstage/plugin-linguist-backend@0.4.3-next.1 - - @backstage/plugin-permission-backend@0.5.26-next.1 - - @backstage/plugin-permission-common@0.7.8-next.0 - - @backstage/plugin-permission-node@0.7.14-next.1 - - @backstage/plugin-proxy-backend@0.3.3-next.1 - - @backstage/plugin-search-backend@1.4.3-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.7-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.7-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.1 - - @backstage/plugin-search-backend-node@1.2.7-next.1 - - @backstage/plugin-todo-backend@0.2.3-next.1 - - @backstage/backend-defaults@0.2.3-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.2.3-next.1 - -## 0.0.15-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.12.2-next.0 - - @backstage/plugin-scaffolder-backend@1.16.3-next.0 - - @backstage/plugin-auth-node@0.3.0-next.0 - - @backstage/plugin-linguist-backend@0.4.2-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.8-next.0 - - @backstage/backend-tasks@0.5.7-next.0 - - @backstage/plugin-app-backend@0.3.50-next.0 - - @backstage/backend-defaults@0.2.2-next.0 - - @backstage/backend-plugin-api@0.6.2-next.0 - - @backstage/plugin-adr-backend@0.3.8-next.0 - - @backstage/plugin-azure-devops-backend@0.3.29-next.0 - - @backstage/plugin-badges-backend@0.2.5-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.2.2-next.0 - - @backstage/plugin-devtools-backend@0.1.5-next.0 - - @backstage/plugin-kubernetes-backend@0.11.5-next.0 - - @backstage/plugin-lighthouse-backend@0.2.6-next.0 - - @backstage/plugin-permission-backend@0.5.25-next.0 - - @backstage/plugin-permission-common@0.7.7 - - @backstage/plugin-permission-node@0.7.13-next.0 - - @backstage/plugin-proxy-backend@0.3.2-next.0 - - @backstage/plugin-search-backend@1.4.2-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.6-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.6-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.6-next.0 - - @backstage/plugin-search-backend-node@1.2.6-next.0 - - @backstage/plugin-techdocs-backend@1.6.7-next.0 - - @backstage/plugin-todo-backend@0.2.2-next.0 - -## 0.0.14 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-search-backend-module-techdocs@0.1.4 - - @backstage/plugin-search-backend-module-catalog@0.1.4 - - @backstage/plugin-search-backend-module-explore@0.1.4 - - @backstage/plugin-azure-devops-backend@0.3.27 - - @backstage/plugin-kubernetes-backend@0.11.3 - - @backstage/plugin-lighthouse-backend@0.2.4 - - @backstage/plugin-permission-backend@0.5.23 - - @backstage/plugin-scaffolder-backend@1.16.0 - - @backstage/backend-defaults@0.2.0 - - @backstage/plugin-devtools-backend@0.1.3 - - @backstage/plugin-techdocs-backend@1.6.5 - - @backstage/plugin-catalog-backend@1.12.0 - - @backstage/plugin-badges-backend@0.2.3 - - @backstage/plugin-search-backend@1.4.0 - - @backstage/plugin-proxy-backend@0.3.0 - - @backstage/plugin-todo-backend@0.2.0 - - @backstage/plugin-app-backend@0.3.48 - - @backstage/backend-plugin-api@0.6.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0 - - @backstage/plugin-entity-feedback-backend@0.1.6 - - @backstage/plugin-search-backend-node@1.2.4 - - @backstage/plugin-linguist-backend@0.4.0 - - @backstage/plugin-auth-node@0.2.17 - - @backstage/backend-tasks@0.5.5 - - @backstage/plugin-adr-backend@0.3.6 - - @backstage/plugin-permission-node@0.7.11 - - @backstage/plugin-permission-common@0.7.7 - -## 0.0.14-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.4-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.4-next.2 - - @backstage/plugin-scaffolder-backend@1.15.2-next.2 - - @backstage/plugin-catalog-backend@1.12.0-next.2 - - @backstage/backend-plugin-api@0.6.0-next.2 - - @backstage/plugin-proxy-backend@0.3.0-next.2 - - @backstage/backend-tasks@0.5.5-next.2 - - @backstage/plugin-app-backend@0.3.48-next.2 - - @backstage/plugin-linguist-backend@0.4.0-next.2 - - @backstage/plugin-techdocs-backend@1.6.5-next.2 - - @backstage/backend-defaults@0.2.0-next.2 - - @backstage/plugin-adr-backend@0.3.6-next.2 - - @backstage/plugin-azure-devops-backend@0.3.27-next.2 - - @backstage/plugin-badges-backend@0.2.3-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.2 - - @backstage/plugin-devtools-backend@0.1.3-next.2 - - @backstage/plugin-entity-feedback-backend@0.1.6-next.2 - - @backstage/plugin-kubernetes-backend@0.11.3-next.2 - - @backstage/plugin-lighthouse-backend@0.2.4-next.2 - - @backstage/plugin-permission-backend@0.5.23-next.2 - - @backstage/plugin-permission-node@0.7.11-next.2 - - @backstage/plugin-search-backend@1.4.0-next.2 - - @backstage/plugin-search-backend-node@1.2.4-next.2 - - @backstage/plugin-todo-backend@0.2.0-next.2 - - @backstage/plugin-auth-node@0.2.17-next.2 - -## 0.0.14-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.4-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.4-next.1 - - @backstage/plugin-azure-devops-backend@0.3.27-next.1 - - @backstage/plugin-kubernetes-backend@0.11.3-next.1 - - @backstage/plugin-lighthouse-backend@0.2.4-next.1 - - @backstage/plugin-permission-backend@0.5.23-next.1 - - @backstage/plugin-scaffolder-backend@1.15.2-next.1 - - @backstage/backend-defaults@0.2.0-next.1 - - @backstage/plugin-devtools-backend@0.1.3-next.1 - - @backstage/plugin-techdocs-backend@1.6.5-next.1 - - @backstage/plugin-catalog-backend@1.12.0-next.1 - - @backstage/plugin-badges-backend@0.2.3-next.1 - - @backstage/plugin-search-backend@1.4.0-next.1 - - @backstage/plugin-todo-backend@0.2.0-next.1 - - @backstage/plugin-app-backend@0.3.48-next.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.1 - - @backstage/plugin-entity-feedback-backend@0.1.6-next.1 - - @backstage/plugin-search-backend-node@1.2.4-next.1 - - @backstage/plugin-linguist-backend@0.3.2-next.1 - - @backstage/plugin-auth-node@0.2.17-next.1 - - @backstage/backend-tasks@0.5.5-next.1 - - @backstage/plugin-adr-backend@0.3.6-next.1 - - @backstage/plugin-permission-node@0.7.11-next.1 - - @backstage/plugin-permission-common@0.7.7 - -## 0.0.14-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-linguist-backend@0.3.2-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.0 - - @backstage/plugin-search-backend-node@1.2.4-next.0 - - @backstage/plugin-todo-backend@0.2.0-next.0 - - @backstage/plugin-catalog-backend@1.12.0-next.0 - - @backstage/plugin-search-backend@1.4.0-next.0 - - @backstage/backend-defaults@0.1.13-next.0 - - @backstage/backend-tasks@0.5.5-next.0 - - @backstage/plugin-adr-backend@0.3.6-next.0 - - @backstage/plugin-app-backend@0.3.48-next.0 - - @backstage/plugin-auth-node@0.2.17-next.0 - - @backstage/plugin-azure-devops-backend@0.3.27-next.0 - - @backstage/plugin-badges-backend@0.2.3-next.0 - - @backstage/plugin-devtools-backend@0.1.3-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.6-next.0 - - @backstage/plugin-kubernetes-backend@0.11.3-next.0 - - @backstage/plugin-lighthouse-backend@0.2.4-next.0 - - @backstage/plugin-permission-backend@0.5.23-next.0 - - @backstage/plugin-permission-common@0.7.7 - - @backstage/plugin-permission-node@0.7.11-next.0 - - @backstage/plugin-scaffolder-backend@1.15.2-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.4-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.4-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.0 - - @backstage/plugin-techdocs-backend@1.6.5-next.0 - -## 0.0.13 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.11.2 - - @backstage/plugin-badges-backend@0.2.2 - - @backstage/plugin-devtools-backend@0.1.2 - - @backstage/plugin-scaffolder-backend@1.15.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1 - - @backstage/plugin-azure-devops-backend@0.3.26 - - @backstage/plugin-linguist-backend@0.3.1 - - @backstage/plugin-adr-backend@0.3.5 - - @backstage/plugin-lighthouse-backend@0.2.3 - - @backstage/plugin-entity-feedback-backend@0.1.5 - - @backstage/plugin-catalog-backend@1.11.0 - - @backstage/backend-defaults@0.1.12 - - @backstage/backend-tasks@0.5.4 - - @backstage/plugin-app-backend@0.3.47 - - @backstage/plugin-auth-node@0.2.16 - - @backstage/plugin-permission-backend@0.5.22 - - @backstage/plugin-permission-common@0.7.7 - - @backstage/plugin-permission-node@0.7.10 - - @backstage/plugin-search-backend@1.3.3 - - @backstage/plugin-search-backend-module-catalog@0.1.3 - - @backstage/plugin-search-backend-module-explore@0.1.3 - - @backstage/plugin-search-backend-module-techdocs@0.1.3 - - @backstage/plugin-search-backend-node@1.2.3 - - @backstage/plugin-techdocs-backend@1.6.4 - - @backstage/plugin-todo-backend@0.1.44 - -## 0.0.13-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-devtools-backend@0.1.2-next.2 - - @backstage/plugin-scaffolder-backend@1.15.1-next.1 - - @backstage/plugin-kubernetes-backend@0.11.2-next.2 - - @backstage/plugin-adr-backend@0.3.5-next.1 - - @backstage/backend-defaults@0.1.12-next.0 - - @backstage/backend-tasks@0.5.4-next.0 - - @backstage/plugin-app-backend@0.3.47-next.0 - - @backstage/plugin-auth-node@0.2.16-next.0 - - @backstage/plugin-azure-devops-backend@0.3.26-next.1 - - @backstage/plugin-badges-backend@0.2.2-next.1 - - @backstage/plugin-catalog-backend@1.11.0-next.0 - - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.5-next.0 - - @backstage/plugin-linguist-backend@0.3.1-next.1 - - @backstage/plugin-permission-backend@0.5.22-next.0 - - @backstage/plugin-permission-common@0.7.7-next.0 - - @backstage/plugin-permission-node@0.7.10-next.0 - - @backstage/plugin-search-backend@1.3.3-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.3-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.3-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.3-next.0 - - @backstage/plugin-search-backend-node@1.2.3-next.0 - - @backstage/plugin-techdocs-backend@1.6.4-next.0 - - @backstage/plugin-todo-backend@0.1.44-next.0 - -## 0.0.13-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.11.2-next.1 - - @backstage/plugin-badges-backend@0.2.2-next.1 - - @backstage/plugin-azure-devops-backend@0.3.26-next.1 - - @backstage/plugin-devtools-backend@0.1.2-next.1 - - @backstage/plugin-linguist-backend@0.3.1-next.1 - -## 0.0.13-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.5-next.0 - - @backstage/plugin-catalog-backend@1.11.0-next.0 - - @backstage/plugin-kubernetes-backend@0.11.2-next.0 - - @backstage/backend-defaults@0.1.12-next.0 - - @backstage/plugin-app-backend@0.3.47-next.0 - - @backstage/plugin-auth-node@0.2.16-next.0 - - @backstage/plugin-permission-backend@0.5.22-next.0 - - @backstage/plugin-permission-common@0.7.7-next.0 - - @backstage/plugin-permission-node@0.7.10-next.0 - - @backstage/plugin-scaffolder-backend@1.15.1-next.0 - - @backstage/plugin-search-backend@1.3.3-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.3-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.3-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.3-next.0 - - @backstage/plugin-search-backend-node@1.2.3-next.0 - - @backstage/plugin-techdocs-backend@1.6.4-next.0 - - @backstage/plugin-todo-backend@0.1.44-next.0 - -## 0.0.12 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.15.0 - - @backstage/plugin-kubernetes-backend@0.11.1 - - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0 - - @backstage/plugin-catalog-backend@1.10.0 - - @backstage/plugin-search-backend@1.3.2 - - @backstage/plugin-search-backend-module-explore@0.1.2 - - @backstage/backend-defaults@0.1.11 - - @backstage/plugin-app-backend@0.3.46 - - @backstage/plugin-auth-node@0.2.15 - - @backstage/plugin-permission-backend@0.5.21 - - @backstage/plugin-permission-node@0.7.9 - - @backstage/plugin-search-backend-module-catalog@0.1.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.2 - - @backstage/plugin-search-backend-node@1.2.2 - - @backstage/plugin-techdocs-backend@1.6.3 - - @backstage/plugin-todo-backend@0.1.43 - - @backstage/plugin-permission-common@0.7.6 - -## 0.0.12-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.15.0-next.3 - - @backstage/plugin-kubernetes-backend@0.11.1-next.3 - - @backstage/plugin-catalog-backend@1.10.0-next.2 - - @backstage/backend-defaults@0.1.11-next.2 - - @backstage/plugin-app-backend@0.3.46-next.2 - - @backstage/plugin-auth-node@0.2.15-next.2 - - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0-next.1 - - @backstage/plugin-permission-backend@0.5.21-next.2 - - @backstage/plugin-permission-common@0.7.6-next.0 - - @backstage/plugin-permission-node@0.7.9-next.2 - - @backstage/plugin-search-backend@1.3.2-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.2-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.2-next.2 - - @backstage/plugin-search-backend-module-techdocs@0.1.2-next.2 - - @backstage/plugin-search-backend-node@1.2.2-next.2 - - @backstage/plugin-techdocs-backend@1.6.3-next.2 - - @backstage/plugin-todo-backend@0.1.43-next.2 - -## 0.0.12-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.11.1-next.2 - - @backstage/plugin-scaffolder-backend@1.15.0-next.2 - -## 0.0.12-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0-next.0 - - @backstage/plugin-catalog-backend@1.9.2-next.1 - - @backstage/plugin-scaffolder-backend@1.15.0-next.1 - - @backstage/backend-defaults@0.1.11-next.1 - - @backstage/plugin-app-backend@0.3.46-next.1 - - @backstage/plugin-auth-node@0.2.15-next.1 - - @backstage/plugin-kubernetes-backend@0.11.1-next.1 - - @backstage/plugin-permission-backend@0.5.21-next.1 - - @backstage/plugin-permission-node@0.7.9-next.1 - - @backstage/plugin-search-backend@1.3.2-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.2-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.2-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.2-next.1 - - @backstage/plugin-search-backend-node@1.2.2-next.1 - - @backstage/plugin-techdocs-backend@1.6.3-next.1 - - @backstage/plugin-todo-backend@0.1.43-next.1 - - @backstage/plugin-permission-common@0.7.6-next.0 - -## 0.0.12-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.14.1-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.2-next.0 - - @backstage/plugin-catalog-backend@1.9.2-next.0 - - @backstage/plugin-kubernetes-backend@0.11.1-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.2-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.2-next.0 - - @backstage/plugin-techdocs-backend@1.6.3-next.0 - - @backstage/plugin-todo-backend@0.1.43-next.0 - - @backstage/plugin-app-backend@0.3.46-next.0 - - @backstage/backend-defaults@0.1.11-next.0 - - @backstage/plugin-auth-node@0.2.15-next.0 - - @backstage/plugin-permission-backend@0.5.21-next.0 - - @backstage/plugin-permission-common@0.7.5 - - @backstage/plugin-permission-node@0.7.9-next.0 - - @backstage/plugin-search-backend@1.3.2-next.0 - - @backstage/plugin-search-backend-node@1.2.2-next.0 - -## 0.0.11 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.14.0 - - @backstage/plugin-catalog-backend@1.9.1 - - @backstage/plugin-kubernetes-backend@0.11.0 - - @backstage/plugin-todo-backend@0.1.42 - - @backstage/plugin-permission-node@0.7.8 - - @backstage/plugin-search-backend@1.3.1 - - @backstage/backend-defaults@0.1.10 - - @backstage/plugin-app-backend@0.3.45 - - @backstage/plugin-auth-node@0.2.14 - - @backstage/plugin-search-backend-module-catalog@0.1.1 - - @backstage/plugin-search-backend-module-explore@0.1.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.1 - - @backstage/plugin-techdocs-backend@1.6.2 - - @backstage/plugin-permission-backend@0.5.20 - - @backstage/plugin-search-backend-node@1.2.1 - - @backstage/plugin-permission-common@0.7.5 - -## 0.0.11-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.9.1-next.2 - - @backstage/plugin-kubernetes-backend@0.11.0-next.2 - - @backstage/plugin-search-backend@1.3.1-next.2 - - @backstage/plugin-scaffolder-backend@1.13.2-next.2 - -## 0.0.11-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.11.0-next.1 - - @backstage/plugin-catalog-backend@1.9.1-next.1 - - @backstage/plugin-scaffolder-backend@1.13.2-next.1 - - @backstage/backend-defaults@0.1.10-next.1 - - @backstage/plugin-app-backend@0.3.45-next.1 - - @backstage/plugin-auth-node@0.2.14-next.1 - - @backstage/plugin-permission-backend@0.5.20-next.1 - - @backstage/plugin-permission-node@0.7.8-next.1 - - @backstage/plugin-search-backend@1.3.1-next.1 - - @backstage/plugin-search-backend-module-catalog@0.1.1-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.1-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.1-next.1 - - @backstage/plugin-search-backend-node@1.2.1-next.1 - - @backstage/plugin-techdocs-backend@1.6.2-next.1 - - @backstage/plugin-todo-backend@0.1.42-next.1 - -## 0.0.11-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-permission-node@0.7.8-next.0 - - @backstage/plugin-scaffolder-backend@1.13.2-next.0 - - @backstage/plugin-kubernetes-backend@0.11.0-next.0 - - @backstage/backend-defaults@0.1.10-next.0 - - @backstage/plugin-app-backend@0.3.45-next.0 - - @backstage/plugin-auth-node@0.2.14-next.0 - - @backstage/plugin-catalog-backend@1.9.1-next.0 - - @backstage/plugin-search-backend@1.3.1-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.1-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.1-next.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.1-next.0 - - @backstage/plugin-techdocs-backend@1.6.2-next.0 - - @backstage/plugin-permission-backend@0.5.20-next.0 - - @backstage/plugin-search-backend-node@1.2.1-next.0 - - @backstage/plugin-todo-backend@0.1.42-next.0 - - @backstage/plugin-permission-common@0.7.5 - -## 0.0.10 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.10.0 - - @backstage/plugin-scaffolder-backend@1.13.0 - - @backstage/plugin-catalog-backend@1.9.0 - - @backstage/plugin-permission-node@0.7.7 - - @backstage/plugin-permission-backend@0.5.19 - - @backstage/plugin-search-backend@1.3.0 - - @backstage/plugin-permission-common@0.7.5 - - @backstage/plugin-techdocs-backend@1.6.1 - - @backstage/plugin-search-backend-node@1.2.0 - - @backstage/plugin-search-backend-module-techdocs@0.1.0 - - @backstage/plugin-search-backend-module-catalog@0.1.0 - - @backstage/plugin-search-backend-module-explore@0.1.0 - - @backstage/backend-defaults@0.1.9 - - @backstage/plugin-app-backend@0.3.44 - - @backstage/plugin-auth-node@0.2.13 - - @backstage/plugin-todo-backend@0.1.41 - -## 0.0.10-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.10.0-next.3 - - @backstage/plugin-catalog-backend@1.9.0-next.3 - - @backstage/plugin-scaffolder-backend@1.13.0-next.3 - - @backstage/backend-defaults@0.1.9-next.2 - - @backstage/plugin-app-backend@0.3.44-next.2 - - @backstage/plugin-auth-node@0.2.13-next.2 - - @backstage/plugin-permission-backend@0.5.19-next.2 - - @backstage/plugin-permission-common@0.7.5-next.0 - - @backstage/plugin-permission-node@0.7.7-next.2 - - @backstage/plugin-search-backend@1.3.0-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.0-next.2 - - @backstage/plugin-search-backend-module-explore@0.1.0-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.0-next.2 - - @backstage/plugin-search-backend-node@1.2.0-next.2 - - @backstage/plugin-techdocs-backend@1.6.1-next.3 - - @backstage/plugin-todo-backend@0.1.41-next.3 - -## 0.0.10-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.10.0-next.2 - - @backstage/plugin-catalog-backend@1.8.1-next.2 - - @backstage/plugin-permission-node@0.7.7-next.2 - - @backstage/plugin-permission-backend@0.5.19-next.2 - - @backstage/plugin-scaffolder-backend@1.13.0-next.2 - - @backstage/backend-defaults@0.1.9-next.2 - - @backstage/plugin-app-backend@0.3.44-next.2 - - @backstage/plugin-auth-node@0.2.13-next.2 - - @backstage/plugin-permission-common@0.7.5-next.0 - - @backstage/plugin-search-backend@1.3.0-next.2 - - @backstage/plugin-search-backend-module-catalog@0.1.0-next.1 - - @backstage/plugin-search-backend-module-explore@0.1.0-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.0-next.1 - - @backstage/plugin-search-backend-node@1.2.0-next.2 - - @backstage/plugin-techdocs-backend@1.6.1-next.2 - - @backstage/plugin-todo-backend@0.1.41-next.2 - -## 0.0.10-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-search-backend@1.3.0-next.1 - - @backstage/plugin-scaffolder-backend@1.13.0-next.1 - - @backstage/plugin-catalog-backend@1.8.1-next.1 - - @backstage/plugin-kubernetes-backend@0.10.0-next.1 - - @backstage/plugin-techdocs-backend@1.6.1-next.1 - - @backstage/plugin-search-backend-node@1.2.0-next.1 - - @backstage/plugin-search-backend-module-techdocs@0.1.0-next.0 - - @backstage/plugin-search-backend-module-catalog@0.1.0-next.0 - - @backstage/plugin-search-backend-module-explore@0.1.0-next.0 - - @backstage/backend-defaults@0.1.9-next.1 - - @backstage/plugin-app-backend@0.3.44-next.1 - - @backstage/plugin-todo-backend@0.1.41-next.1 - -## 0.0.10-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.12.1-next.0 - - @backstage/plugin-catalog-backend@1.8.1-next.0 - - @backstage/backend-defaults@0.1.9-next.0 - - @backstage/plugin-app-backend@0.3.44-next.0 - - @backstage/plugin-techdocs-backend@1.6.1-next.0 - - @backstage/plugin-todo-backend@0.1.41-next.0 - -## 0.0.9 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.12.0 - - @backstage/plugin-catalog-backend@1.8.0 - - @backstage/plugin-todo-backend@0.1.40 - - @backstage/plugin-techdocs-backend@1.6.0 - - @backstage/backend-defaults@0.1.8 - - @backstage/plugin-app-backend@0.3.43 - -## 0.0.9-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.12.0-next.2 - - @backstage/backend-defaults@0.1.8-next.2 - - @backstage/plugin-app-backend@0.3.43-next.2 - - @backstage/plugin-catalog-backend@1.8.0-next.2 - - @backstage/plugin-todo-backend@0.1.40-next.2 - -## 0.0.9-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.12.0-next.1 - - @backstage/plugin-app-backend@0.3.43-next.1 - - @backstage/plugin-catalog-backend@1.8.0-next.1 - - @backstage/plugin-todo-backend@0.1.40-next.1 - - @backstage/backend-defaults@0.1.8-next.1 - -## 0.0.9-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-todo-backend@0.1.40-next.0 - - @backstage/plugin-scaffolder-backend@1.11.1-next.0 - - @backstage/plugin-catalog-backend@1.8.0-next.0 - - @backstage/backend-defaults@0.1.8-next.0 - - @backstage/plugin-app-backend@0.3.43-next.0 - -## 0.0.8 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.7.2 - - @backstage/plugin-scaffolder-backend@1.11.0 - - @backstage/plugin-app-backend@0.3.42 - - @backstage/backend-defaults@0.1.7 - -## 0.0.8-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.7.2-next.2 - - @backstage/plugin-scaffolder-backend@1.11.0-next.2 - - @backstage/plugin-app-backend@0.3.42-next.2 - - @backstage/backend-defaults@0.1.7-next.2 - -## 0.0.8-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.7.2-next.1 - - @backstage/plugin-scaffolder-backend@1.11.0-next.1 - - @backstage/backend-defaults@0.1.7-next.1 - - @backstage/plugin-app-backend@0.3.42-next.1 - -## 0.0.8-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.11.0-next.0 - - @backstage/backend-defaults@0.1.7-next.0 - - @backstage/plugin-app-backend@0.3.42-next.0 - - @backstage/plugin-catalog-backend@1.7.2-next.0 - -## 0.0.7 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.10.0 - - @backstage/backend-defaults@0.1.5 - - @backstage/plugin-app-backend@0.3.40 - - @backstage/plugin-catalog-backend@1.7.0 - -## 0.0.7-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-defaults@0.1.5-next.1 - - @backstage/plugin-scaffolder-backend@1.10.0-next.2 - - @backstage/plugin-catalog-backend@1.7.0-next.2 - - @backstage/plugin-app-backend@0.3.40-next.1 - -## 0.0.7-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-defaults@0.1.5-next.0 - - @backstage/plugin-scaffolder-backend@1.10.0-next.1 - - @backstage/plugin-app-backend@0.3.40-next.0 - - @backstage/plugin-catalog-backend@1.7.0-next.1 - -## 0.0.7-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.9.1-next.0 - - @backstage/plugin-catalog-backend@1.7.0-next.0 - - @backstage/backend-defaults@0.1.4 - - @backstage/plugin-app-backend@0.3.39 - -## 0.0.6 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.9.0 - - @backstage/plugin-catalog-backend@1.6.0 - - @backstage/plugin-app-backend@0.3.39 - - @backstage/backend-defaults@0.1.4 - -## 0.0.6-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.6.0-next.3 - - @backstage/plugin-scaffolder-backend@1.9.0-next.3 - - @backstage/backend-defaults@0.1.4-next.3 - - @backstage/plugin-app-backend@0.3.39-next.3 - -## 0.0.6-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.6.0-next.2 - - @backstage/plugin-app-backend@0.3.39-next.2 - - @backstage/plugin-scaffolder-backend@1.9.0-next.2 - - @backstage/backend-defaults@0.1.4-next.2 - -## 0.0.6-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.6.0-next.1 - - @backstage/plugin-scaffolder-backend@1.8.1-next.1 - - @backstage/plugin-app-backend@0.3.39-next.1 - - @backstage/backend-defaults@0.1.4-next.1 - -## 0.0.6-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.8.1-next.0 - - @backstage/plugin-catalog-backend@1.6.0-next.0 - - @backstage/plugin-app-backend@0.3.39-next.0 - - @backstage/backend-defaults@0.1.4-next.0 - -## 0.0.5 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.5.1 - - @backstage/plugin-scaffolder-backend@1.8.0 - - @backstage/plugin-app-backend@0.3.38 - - @backstage/backend-defaults@0.1.3 - -## 0.0.5-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.8.0-next.2 - - @backstage/plugin-app-backend@0.3.38-next.1 - - @backstage/plugin-catalog-backend@1.5.1-next.1 - - @backstage/backend-defaults@0.1.3-next.1 - -## 0.0.5-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.8.0-next.1 - -## 0.0.5-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.5.1-next.0 - - @backstage/plugin-scaffolder-backend@1.8.0-next.0 - - @backstage/plugin-app-backend@0.3.38-next.0 - - @backstage/backend-defaults@0.1.3-next.0 - -## 0.0.4 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.5.0 - - @backstage/plugin-scaffolder-backend@1.7.0 - - @backstage/backend-defaults@0.1.2 - - @backstage/plugin-app-backend@0.3.37 - -## 0.0.4-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.5.0-next.2 - - @backstage/plugin-scaffolder-backend@1.7.0-next.2 - - @backstage/plugin-app-backend@0.3.37-next.2 - - @backstage/backend-defaults@0.1.2-next.2 - -## 0.0.4-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.7.0-next.1 - - @backstage/backend-defaults@0.1.2-next.1 - - @backstage/plugin-app-backend@0.3.37-next.1 - - @backstage/plugin-catalog-backend@1.4.1-next.1 - -## 0.0.4-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.7.0-next.0 - - @backstage/backend-defaults@0.1.2-next.0 - - @backstage/plugin-catalog-backend@1.4.1-next.0 - - @backstage/plugin-app-backend@0.3.37-next.0 - -## 0.0.3 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.6.0 - - @backstage/plugin-catalog-backend@1.4.0 - - @backstage/backend-defaults@0.1.1 - -## 0.0.3-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.4.0-next.1 - - @backstage/plugin-scaffolder-backend@1.6.0-next.1 - -## 0.0.3-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.6.0-next.0 - - @backstage/plugin-catalog-backend@1.3.2-next.0 - - @backstage/backend-defaults@0.1.1-next.0 - -## 0.0.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.5.0 - - @backstage/backend-defaults@0.1.0 - - @backstage/plugin-catalog-backend@1.3.1 - -## 0.0.2-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.5.0-next.0 - - @backstage/backend-app-api@0.1.1-next.0 - - @backstage/plugin-catalog-backend@1.3.1-next.0 - -## 0.0.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.3.0 - - @backstage/plugin-scaffolder-backend@1.4.0 - - @backstage/backend-app-api@0.1.0 - -## 0.0.1-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.3.0-next.3 - - @backstage/plugin-scaffolder-backend@1.4.0-next.3 - - @backstage/backend-app-api@0.1.0-next.0 diff --git a/packages/backend-next/README.md b/packages/backend-next/README.md deleted file mode 100644 index 4634e5673f..0000000000 --- a/packages/backend-next/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# example-backend-next - -This is an example backend for [the new Backstage backend system](https://backstage.io/docs/backend-system/). - -Do not use this in your own projects. diff --git a/packages/backend-next/knip-report.md b/packages/backend-next/knip-report.md deleted file mode 100644 index a26b412ee9..0000000000 --- a/packages/backend-next/knip-report.md +++ /dev/null @@ -1,12 +0,0 @@ -# Knip report - -## Unused dependencies (5) - -| Name | Location | Severity | -| :----------------------------------------------- | :----------- | :------- | -| @backstage/plugin-catalog-backend-module-openapi | package.json | error | -| @backstage/plugin-search-backend-node | package.json | error | -| @backstage/plugin-permission-common | package.json | error | -| @backstage/plugin-permission-node | package.json | error | -| @backstage/backend-tasks | package.json | error | - diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts deleted file mode 100644 index a4acd19cf2..0000000000 --- a/packages/backend-next/src/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { createBackend } from '@backstage/backend-defaults'; - -const backend = createBackend(); - -backend.add(import('@backstage/plugin-auth-backend')); -backend.add(import('./authModuleGithubProvider')); -backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); - -backend.add(import('@backstage/plugin-app-backend/alpha')); -backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed')); -backend.add( - import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), -); -backend.add(import('@backstage/plugin-catalog-backend/alpha')); -backend.add(import('@backstage/plugin-devtools-backend')); -backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); -backend.add( - import('@backstage/plugin-permission-backend-module-allow-all-policy'), -); -backend.add(import('@backstage/plugin-permission-backend/alpha')); -backend.add(import('@backstage/plugin-proxy-backend/alpha')); -backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); -backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); -backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-explore/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); -backend.add( - import('@backstage/plugin-catalog-backend-module-backstage-openapi'), -); -backend.add(import('@backstage/plugin-search-backend/alpha')); -backend.add(import('@backstage/plugin-techdocs-backend/alpha')); -backend.add(import('@backstage/plugin-signals-backend')); -backend.add(import('@backstage/plugin-notifications-backend')); - -backend.start(); diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 06aba6c5ca..29e3aecc7c 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,119 +1,107 @@ # example-backend -## 0.2.98-next.1 +## 0.0.26-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 - - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-notifications-backend@0.2.1-next.1 - @backstage/plugin-catalog-backend@1.22.0-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 - @backstage/plugin-scaffolder-backend@1.22.5-next.1 - - example-app@0.2.97-next.1 - @backstage/plugin-search-backend@1.5.8-next.1 + - @backstage/backend-defaults@0.2.18-next.1 - @backstage/plugin-app-backend@0.3.66-next.1 - @backstage/plugin-kubernetes-backend@0.17.1-next.1 - @backstage/backend-tasks@0.5.23-next.1 - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.1 - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.1 - @backstage/plugin-devtools-backend@0.3.4-next.1 - - @backstage/plugin-events-backend@0.3.5-next.1 - - @backstage/plugin-events-node@0.3.4-next.1 - @backstage/plugin-permission-backend@0.5.42-next.1 - @backstage/plugin-permission-node@0.7.29-next.1 - @backstage/plugin-proxy-backend@0.4.16-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.1 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.1 - @backstage/plugin-search-backend-module-explore@0.1.24-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.27-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 - @backstage/plugin-search-backend-node@1.2.22-next.1 - @backstage/plugin-signals-backend@0.1.4-next.1 - - @backstage/plugin-signals-node@0.1.4-next.1 - @backstage/plugin-techdocs-backend@1.10.5-next.1 - - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.1 -## 0.2.98-next.0 +## 0.0.26-next.0 ### Patch Changes - Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.0 - @backstage/plugin-catalog-backend@1.22.0-next.0 - @backstage/plugin-scaffolder-backend@1.22.5-next.0 - @backstage/catalog-model@1.5.0-next.0 - @backstage/plugin-search-backend-node@1.2.22-next.0 - @backstage/plugin-search-backend@1.5.8-next.0 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.0 - @backstage/plugin-search-backend-module-explore@0.1.23-next.0 - @backstage/plugin-auth-backend@0.22.5-next.0 - @backstage/plugin-auth-node@0.4.13-next.0 - - @backstage/backend-common@0.21.8-next.0 - - example-app@0.2.97-next.0 + - @backstage/plugin-notifications-backend@0.2.1-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.0 + - @backstage/backend-defaults@0.2.18-next.0 - @backstage/plugin-app-backend@0.3.66-next.0 - @backstage/plugin-kubernetes-backend@0.17.1-next.0 - - @backstage/catalog-client@1.6.5-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 - - @backstage/plugin-catalog-node@1.11.2-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 - @backstage/plugin-techdocs-backend@1.10.5-next.0 - @backstage/backend-tasks@0.5.23-next.0 - - @backstage/config@1.2.0 - - @backstage/integration@1.10.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.0 - @backstage/plugin-devtools-backend@0.3.4-next.0 - - @backstage/plugin-events-backend@0.3.5-next.0 - - @backstage/plugin-events-node@0.3.4-next.0 - @backstage/plugin-permission-backend@0.5.42-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.0 - @backstage/plugin-permission-common@0.7.13 - @backstage/plugin-permission-node@0.7.29-next.0 - @backstage/plugin-proxy-backend@0.4.16-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.27-next.0 - @backstage/plugin-signals-backend@0.1.4-next.0 - - @backstage/plugin-signals-node@0.1.4-next.0 -## 0.2.97 +## 0.0.25 ### Patch Changes - Updated dependencies - - @backstage/plugin-search-backend-module-pg@0.5.26 - @backstage/plugin-badges-backend@0.4.0 - @backstage/plugin-kubernetes-backend@0.17.0 - - @backstage/backend-common@0.21.7 - @backstage/plugin-azure-devops-backend@0.6.4 - @backstage/plugin-techdocs-backend@1.10.4 + - @backstage/plugin-notifications-backend@0.2.0 - @backstage/plugin-permission-node@0.7.28 - @backstage/plugin-auth-backend@0.22.4 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.3 - @backstage/plugin-catalog-backend@1.21.1 - - @backstage/plugin-events-backend@0.3.4 - - @backstage/plugin-tech-insights-node@0.6.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.0 + - @backstage/backend-plugin-api@0.6.17 - @backstage/plugin-search-backend@1.5.7 - @backstage/plugin-todo-backend@0.3.16 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.49 + - @backstage/plugin-scaffolder-backend-module-github@0.2.7 - @backstage/plugin-search-backend-module-techdocs@0.1.22 - @backstage/plugin-search-backend-module-explore@0.1.21 - @backstage/plugin-entity-feedback-backend@0.2.14 - - @backstage/plugin-code-coverage-backend@0.2.31 - - @backstage/plugin-tech-insights-backend@0.5.31 - @backstage/plugin-search-backend-node@1.2.21 - @backstage/plugin-lighthouse-backend@0.4.10 - @backstage/plugin-permission-backend@0.5.41 + - @backstage/plugin-sonarqube-backend@0.2.19 - @backstage/plugin-devtools-backend@0.3.3 - @backstage/plugin-linguist-backend@0.5.15 - @backstage/plugin-playlist-backend@0.3.21 - - @backstage/plugin-explore-backend@0.0.27 - @backstage/plugin-jenkins-backend@0.4.4 - @backstage/backend-tasks@0.5.22 - - @backstage/plugin-kafka-backend@0.3.15 - @backstage/plugin-nomad-backend@0.1.19 - @backstage/plugin-adr-backend@0.4.14 - @backstage/plugin-app-backend@0.3.65 @@ -121,218 +109,174 @@ - @backstage/plugin-signals-backend@0.1.3 - @backstage/plugin-proxy-backend@0.4.15 - @backstage/plugin-scaffolder-backend@1.22.4 - - @backstage/catalog-client@1.6.4 - - @backstage/integration@1.10.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.4.0 - - example-app@0.2.96 + - @backstage/backend-defaults@0.2.17 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.3 + - @backstage/plugin-catalog-backend-module-openapi@0.1.35 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4 - - @backstage/plugin-events-node@0.3.3 - - @backstage/plugin-rollbar-backend@0.1.62 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.18 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.34 - @backstage/plugin-search-backend-module-catalog@0.1.22 - - @backstage/plugin-signals-node@0.1.3 - - @backstage/plugin-catalog-node@1.11.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.14 - @backstage/catalog-model@1.4.5 - - @backstage/config@1.2.0 - - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.14 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15 - @backstage/plugin-permission-common@0.7.13 -## 0.2.97-next.1 +## 0.0.25-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-kubernetes-backend@0.17.0-next.1 - - @backstage/backend-common@0.21.7-next.1 - @backstage/plugin-azure-devops-backend@0.6.4-next.1 - @backstage/plugin-techdocs-backend@1.10.4-next.1 - - @backstage/plugin-events-backend@0.3.4-next.1 - @backstage/plugin-auth-backend@0.22.4-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 - @backstage/plugin-auth-node@0.4.12-next.1 - @backstage/plugin-proxy-backend@0.4.15-next.1 - @backstage/plugin-scaffolder-backend@1.22.4-next.1 - @backstage/plugin-catalog-backend@1.21.1-next.1 - - @backstage/catalog-client@1.6.4-next.0 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.3-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.7-next.1 - @backstage/plugin-app-backend@0.3.65-next.1 + - @backstage/plugin-notifications-backend@0.2.0-next.1 + - @backstage/backend-defaults@0.2.17-next.1 - @backstage/backend-tasks@0.5.22-next.1 - @backstage/plugin-adr-backend@0.4.14-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.3-next.1 - @backstage/plugin-badges-backend@0.3.14-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.35-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4-next.1 - - @backstage/plugin-code-coverage-backend@0.2.31-next.1 - @backstage/plugin-devtools-backend@0.3.3-next.1 - @backstage/plugin-entity-feedback-backend@0.2.14-next.1 - - @backstage/plugin-events-node@0.3.3-next.1 - - @backstage/plugin-explore-backend@0.0.27-next.1 - @backstage/plugin-jenkins-backend@0.4.4-next.1 - - @backstage/plugin-kafka-backend@0.3.15-next.1 - @backstage/plugin-lighthouse-backend@0.4.10-next.1 - @backstage/plugin-linguist-backend@0.5.15-next.1 - @backstage/plugin-nomad-backend@0.1.19-next.1 - @backstage/plugin-permission-backend@0.5.41-next.1 - @backstage/plugin-permission-node@0.7.28-next.1 - @backstage/plugin-playlist-backend@0.3.21-next.1 - - @backstage/plugin-rollbar-backend@0.1.62-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.18-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.34-next.1 - @backstage/plugin-search-backend@1.5.7-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.22-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.20-next.1 - @backstage/plugin-search-backend-module-explore@0.1.21-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.26-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.22-next.1 - @backstage/plugin-search-backend-node@1.2.21-next.1 - @backstage/plugin-signals-backend@0.1.3-next.1 - - @backstage/plugin-signals-node@0.1.3-next.1 - - @backstage/plugin-tech-insights-backend@0.5.31-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.49-next.1 - - @backstage/plugin-tech-insights-node@0.5.3-next.1 + - @backstage/plugin-sonarqube-backend@0.2.19-next.1 - @backstage/plugin-todo-backend@0.3.16-next.1 - - example-app@0.2.96-next.1 - @backstage/catalog-model@1.4.5 - - @backstage/config@1.2.0 - - @backstage/integration@1.10.0-next.0 - - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.14-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.11-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15-next.1 - - @backstage/plugin-catalog-node@1.11.1-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.14-next.1 - @backstage/plugin-permission-common@0.7.13 -## 0.2.97-next.0 +## 0.0.25-next.0 ### Patch Changes - Updated dependencies - @backstage/plugin-techdocs-backend@1.10.4-next.0 - @backstage/plugin-catalog-backend@1.21.1-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.11-next.0 - @backstage/plugin-kubernetes-backend@0.16.4-next.0 - @backstage/plugin-signals-backend@0.1.3-next.0 - - @backstage/backend-common@0.21.7-next.0 - - @backstage/integration@1.10.0-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.22-next.0 - @backstage/plugin-scaffolder-backend@1.22.4-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.35-next.0 + - @backstage/backend-defaults@0.2.17-next.0 - @backstage/plugin-app-backend@0.3.65-next.0 - - example-app@0.2.96-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 - @backstage/backend-tasks@0.5.22-next.0 - - @backstage/catalog-client@1.6.3 - @backstage/catalog-model@1.4.5 - - @backstage/config@1.2.0 - @backstage/plugin-adr-backend@0.4.14-next.0 - @backstage/plugin-auth-backend@0.22.4-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.14-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.3-next.0 - @backstage/plugin-auth-node@0.4.12-next.0 - @backstage/plugin-azure-devops-backend@0.6.4-next.0 - - @backstage/plugin-azure-sites-common@0.1.3 - @backstage/plugin-badges-backend@0.3.14-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.15-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.4-next.0 - - @backstage/plugin-catalog-node@1.11.1-next.0 - - @backstage/plugin-code-coverage-backend@0.2.31-next.0 - @backstage/plugin-devtools-backend@0.3.3-next.0 - @backstage/plugin-entity-feedback-backend@0.2.14-next.0 - - @backstage/plugin-events-backend@0.3.3-next.0 - - @backstage/plugin-events-node@0.3.3-next.0 - - @backstage/plugin-explore-backend@0.0.27-next.0 - @backstage/plugin-jenkins-backend@0.4.4-next.0 - - @backstage/plugin-kafka-backend@0.3.15-next.0 - @backstage/plugin-lighthouse-backend@0.4.10-next.0 - @backstage/plugin-linguist-backend@0.5.15-next.0 - @backstage/plugin-nomad-backend@0.1.19-next.0 + - @backstage/plugin-notifications-backend@0.1.3-next.0 - @backstage/plugin-permission-backend@0.5.41-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.14-next.0 - @backstage/plugin-permission-common@0.7.13 - @backstage/plugin-permission-node@0.7.28-next.0 - @backstage/plugin-playlist-backend@0.3.21-next.0 - @backstage/plugin-proxy-backend@0.4.15-next.0 - - @backstage/plugin-rollbar-backend@0.1.62-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.18-next.0 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.3-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.34-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.7-next.0 - @backstage/plugin-search-backend@1.5.7-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.22-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.20-next.0 - @backstage/plugin-search-backend-module-explore@0.1.21-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.26-next.0 - @backstage/plugin-search-backend-node@1.2.21-next.0 - - @backstage/plugin-signals-node@0.1.3-next.0 - - @backstage/plugin-tech-insights-backend@0.5.31-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.49-next.0 - - @backstage/plugin-tech-insights-node@0.5.3-next.0 + - @backstage/plugin-sonarqube-backend@0.2.19-next.0 - @backstage/plugin-todo-backend@0.3.16-next.0 -## 0.2.96 +## 0.0.24 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.21.0 - - @backstage/plugin-catalog-node@1.11.0 - @backstage/plugin-kubernetes-backend@0.16.3 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.3 - @backstage/plugin-permission-backend@0.5.40 - @backstage/plugin-proxy-backend@0.4.14 - @backstage/plugin-scaffolder-backend@1.22.3 - - @backstage/catalog-client@1.6.3 - @backstage/plugin-jenkins-backend@0.4.3 - @backstage/plugin-auth-backend@0.22.3 - @backstage/plugin-auth-node@0.4.11 - - @backstage/backend-common@0.21.6 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.34 - @backstage/plugin-azure-devops-backend@0.6.3 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.10 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.14 - @backstage/plugin-lighthouse-backend@0.4.9 - @backstage/plugin-linguist-backend@0.5.14 - @backstage/plugin-search-backend-module-catalog@0.1.21 - @backstage/plugin-search-backend-module-techdocs@0.1.21 - @backstage/plugin-todo-backend@0.3.15 - - example-app@0.2.95 + - @backstage/backend-defaults@0.2.16 - @backstage/plugin-app-backend@0.3.64 - @backstage/plugin-adr-backend@0.4.13 - @backstage/plugin-badges-backend@0.3.13 - - @backstage/plugin-code-coverage-backend@0.2.30 - @backstage/plugin-entity-feedback-backend@0.2.13 + - @backstage/plugin-notifications-backend@0.1.2 - @backstage/plugin-playlist-backend@0.3.20 - - @backstage/plugin-tech-insights-backend@0.5.30 - @backstage/plugin-techdocs-backend@1.10.3 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.13 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.13 - @backstage/plugin-permission-node@0.7.27 - @backstage/plugin-signals-backend@0.1.2 - - @backstage/plugin-signals-node@0.1.2 - @backstage/backend-tasks@0.5.21 - @backstage/plugin-devtools-backend@0.3.2 - - @backstage/plugin-events-backend@0.3.2 - - @backstage/plugin-events-node@0.3.2 - - @backstage/plugin-explore-backend@0.0.26 - - @backstage/plugin-kafka-backend@0.3.14 - @backstage/plugin-nomad-backend@0.1.18 - - @backstage/plugin-rollbar-backend@0.1.61 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.17 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.33 + - @backstage/plugin-scaffolder-backend-module-github@0.2.6 - @backstage/plugin-search-backend@1.5.6 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.19 - @backstage/plugin-search-backend-module-explore@0.1.20 - - @backstage/plugin-search-backend-module-pg@0.5.25 - @backstage/plugin-search-backend-node@1.2.20 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.48 - - @backstage/plugin-tech-insights-node@0.5.2 + - @backstage/plugin-sonarqube-backend@0.2.18 - @backstage/catalog-model@1.4.5 - - @backstage/config@1.2.0 - - @backstage/integration@1.9.1 - - @backstage/plugin-azure-sites-common@0.1.3 - @backstage/plugin-permission-common@0.7.13 -## 0.2.95 +## 0.0.23 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.20.0 - - @backstage/plugin-catalog-node@1.10.0 - @backstage/plugin-kubernetes-backend@0.16.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.2 - @backstage/plugin-permission-backend@0.5.39 - - @backstage/catalog-client@1.6.2 - - @backstage/backend-common@0.21.5 + - @backstage/plugin-catalog-backend-module-openapi@0.1.33 - @backstage/plugin-auth-backend@0.22.2 - @backstage/plugin-azure-devops-backend@0.6.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.9 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.13 - @backstage/plugin-jenkins-backend@0.4.2 - @backstage/plugin-lighthouse-backend@0.4.8 @@ -341,55 +285,45 @@ - @backstage/plugin-search-backend-module-catalog@0.1.20 - @backstage/plugin-search-backend-module-techdocs@0.1.20 - @backstage/plugin-todo-backend@0.3.14 - - example-app@0.2.94 + - @backstage/backend-defaults@0.2.15 - @backstage/plugin-app-backend@0.3.63 - @backstage/plugin-adr-backend@0.4.12 - @backstage/plugin-auth-node@0.4.10 - @backstage/plugin-badges-backend@0.3.12 - - @backstage/plugin-code-coverage-backend@0.2.29 - @backstage/plugin-entity-feedback-backend@0.2.12 + - @backstage/plugin-notifications-backend@0.1.1 - @backstage/plugin-playlist-backend@0.3.19 - - @backstage/plugin-tech-insights-backend@0.5.29 - @backstage/plugin-techdocs-backend@1.10.2 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.1 - @backstage/backend-tasks@0.5.20 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.1 - @backstage/plugin-devtools-backend@0.3.1 - - @backstage/plugin-events-backend@0.3.1 - - @backstage/plugin-events-node@0.3.1 - - @backstage/plugin-explore-backend@0.0.25 - - @backstage/plugin-kafka-backend@0.3.13 - @backstage/plugin-nomad-backend@0.1.17 - @backstage/plugin-permission-node@0.7.26 - @backstage/plugin-proxy-backend@0.4.13 - - @backstage/plugin-rollbar-backend@0.1.60 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.16 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.32 + - @backstage/plugin-scaffolder-backend-module-github@0.2.5 - @backstage/plugin-search-backend@1.5.5 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.18 - @backstage/plugin-search-backend-module-explore@0.1.19 - - @backstage/plugin-search-backend-module-pg@0.5.24 - @backstage/plugin-search-backend-node@1.2.19 - @backstage/plugin-signals-backend@0.1.1 - - @backstage/plugin-signals-node@0.1.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.47 - - @backstage/plugin-tech-insights-node@0.5.1 + - @backstage/plugin-sonarqube-backend@0.2.17 + - @backstage/backend-plugin-api@0.6.15 - @backstage/catalog-model@1.4.5 - - @backstage/config@1.2.0 - - @backstage/integration@1.9.1 - - @backstage/plugin-azure-sites-common@0.1.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.12 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.12 - @backstage/plugin-permission-common@0.7.13 -## 0.2.94 +## 0.0.22 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.19.0 - - @backstage/plugin-catalog-node@1.9.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.1 - @backstage/plugin-permission-backend@0.5.38 + - @backstage/plugin-catalog-backend-module-openapi@0.1.32 - @backstage/plugin-auth-backend@0.22.1 - @backstage/plugin-azure-devops-backend@0.6.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.8 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.12 - @backstage/plugin-jenkins-backend@0.4.1 - @backstage/plugin-kubernetes-backend@0.16.1 @@ -399,157 +333,130 @@ - @backstage/plugin-search-backend-module-catalog@0.1.19 - @backstage/plugin-search-backend-module-techdocs@0.1.19 - @backstage/plugin-todo-backend@0.3.13 + - @backstage/plugin-auth-backend-module-github-provider@0.1.11 - @backstage/plugin-techdocs-backend@1.10.1 -## 0.2.93 +## 0.0.21 ### Patch Changes - Updated dependencies - - @backstage/plugin-events-backend@0.3.0 - - @backstage/plugin-events-node@0.3.0 + - @backstage/plugin-notifications-backend@0.1.0 - @backstage/plugin-scaffolder-backend@1.22.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.31 - @backstage/plugin-linguist-backend@0.5.11 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.7 - @backstage/plugin-catalog-backend-module-unprocessed@0.4.0 - @backstage/plugin-catalog-backend@1.18.0 - - @backstage/plugin-code-coverage-backend@0.2.28 - @backstage/plugin-devtools-backend@0.3.0 - @backstage/plugin-jenkins-backend@0.4.0 - @backstage/plugin-search-backend@1.5.4 - - @backstage/backend-common@0.21.4 - - @backstage/integration@1.9.1 - @backstage/plugin-auth-node@0.4.9 - @backstage/plugin-lighthouse-backend@0.4.6 - - @backstage/config@1.2.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.0 - @backstage/plugin-azure-devops-backend@0.6.0 - @backstage/plugin-permission-backend@0.5.37 - @backstage/plugin-signals-backend@0.1.0 - @backstage/plugin-nomad-backend@0.1.16 - @backstage/plugin-entity-feedback-backend@0.2.11 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.17 - - @backstage/plugin-search-backend-module-pg@0.5.23 - - @backstage/plugin-signals-node@0.1.0 - @backstage/plugin-playlist-backend@0.3.18 + - @backstage/backend-plugin-api@0.6.14 - @backstage/plugin-auth-backend@0.22.0 - @backstage/plugin-techdocs-backend@1.10.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.15 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.4 - @backstage/plugin-permission-common@0.7.13 - @backstage/plugin-search-backend-module-techdocs@0.1.18 - @backstage/plugin-search-backend-module-catalog@0.1.18 - @backstage/plugin-search-backend-module-explore@0.1.18 - - @backstage/plugin-catalog-node@1.8.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.46 - - @backstage/catalog-client@1.6.1 + - @backstage/backend-defaults@0.2.14 - @backstage/plugin-kubernetes-backend@0.16.0 - @backstage/plugin-adr-backend@0.4.11 - @backstage/plugin-proxy-backend@0.4.12 - @backstage/backend-tasks@0.5.19 - @backstage/plugin-search-backend-node@1.2.18 - - @backstage/plugin-tech-insights-backend@0.5.28 - @backstage/plugin-app-backend@0.3.62 - @backstage/plugin-permission-node@0.7.25 - @backstage/plugin-todo-backend@0.3.12 - - @backstage/plugin-tech-insights-node@0.5.0 - @backstage/plugin-badges-backend@0.3.11 - - example-app@0.2.93 + - @backstage/plugin-auth-backend-module-github-provider@0.1.11 + - @backstage/plugin-catalog-backend-module-openapi@0.1.31 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11 - - @backstage/plugin-explore-backend@0.0.24 - - @backstage/plugin-rollbar-backend@0.1.59 - - @backstage/plugin-kafka-backend@0.3.12 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.11 + - @backstage/plugin-sonarqube-backend@0.2.16 - @backstage/catalog-model@1.4.5 - - @backstage/plugin-azure-sites-common@0.1.3 -## 0.2.93-next.2 +## 0.0.21-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.22.0-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.31-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.7-next.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.11-next.2 - @backstage/plugin-catalog-backend@1.18.0-next.2 - - @backstage/plugin-code-coverage-backend@0.2.28-next.2 - @backstage/plugin-devtools-backend@0.3.0-next.2 - @backstage/plugin-jenkins-backend@0.4.0-next.2 - @backstage/plugin-search-backend@1.5.4-next.2 - - @backstage/integration@1.9.1-next.2 - @backstage/plugin-techdocs-backend@1.10.0-next.2 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.0-next.2 - - @backstage/plugin-signals-node@0.1.0-next.2 - - @backstage/catalog-client@1.6.1-next.1 + - @backstage/plugin-notifications-backend@0.1.0-next.2 - @backstage/plugin-linguist-backend@0.5.11-next.2 - @backstage/plugin-kubernetes-backend@0.16.0-next.2 - @backstage/plugin-todo-backend@0.3.12-next.2 - @backstage/plugin-signals-backend@0.1.0-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.15-next.2 - - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.2.4-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.31-next.2 - @backstage/plugin-adr-backend@0.4.11-next.2 - @backstage/plugin-azure-devops-backend@0.6.0-next.2 - - example-app@0.2.93-next.2 - @backstage/plugin-search-backend-module-techdocs@0.1.18-next.2 - @backstage/plugin-auth-backend@0.22.0-next.2 + - @backstage/backend-defaults@0.2.14-next.2 - @backstage/plugin-app-backend@0.3.62-next.2 - @backstage/plugin-auth-node@0.4.9-next.2 - @backstage/plugin-badges-backend@0.3.11-next.2 - - @backstage/plugin-catalog-node@1.8.0-next.2 - @backstage/plugin-entity-feedback-backend@0.2.11-next.2 - @backstage/plugin-lighthouse-backend@0.4.6-next.2 - @backstage/plugin-playlist-backend@0.3.18-next.2 - @backstage/plugin-search-backend-module-catalog@0.1.18-next.2 - - @backstage/plugin-tech-insights-backend@0.5.28-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 - @backstage/backend-tasks@0.5.19-next.2 - @backstage/catalog-model@1.4.5-next.0 - - @backstage/config@1.2.0-next.1 - - @backstage/plugin-azure-sites-common@0.1.3-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.11-next.2 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.0-next.2 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11-next.2 - - @backstage/plugin-events-backend@0.3.0-next.2 - - @backstage/plugin-events-node@0.3.0-next.2 - - @backstage/plugin-explore-backend@0.0.24-next.2 - - @backstage/plugin-kafka-backend@0.3.12-next.2 - @backstage/plugin-nomad-backend@0.1.16-next.2 - @backstage/plugin-permission-backend@0.5.37-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.11-next.2 - @backstage/plugin-permission-common@0.7.13-next.1 - @backstage/plugin-permission-node@0.7.25-next.2 - @backstage/plugin-proxy-backend@0.4.12-next.2 - - @backstage/plugin-rollbar-backend@0.1.59-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.17-next.2 - @backstage/plugin-search-backend-module-explore@0.1.18-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.23-next.2 - @backstage/plugin-search-backend-node@1.2.18-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.46-next.2 - - @backstage/plugin-tech-insights-node@0.5.0-next.2 + - @backstage/plugin-sonarqube-backend@0.2.16-next.2 -## 0.2.93-next.1 +## 0.0.21-next.1 ### Patch Changes - Updated dependencies - - @backstage/config@1.2.0-next.1 - @backstage/plugin-entity-feedback-backend@0.2.11-next.1 + - @backstage/plugin-notifications-backend@0.1.0-next.1 - @backstage/plugin-scaffolder-backend@1.22.0-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.17-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.4-next.1 - @backstage/plugin-app-backend@0.3.62-next.1 - @backstage/plugin-signals-backend@0.1.0-next.1 - - @backstage/plugin-signals-node@0.1.0-next.1 - @backstage/plugin-azure-devops-backend@0.6.0-next.1 - @backstage/plugin-kubernetes-backend@0.16.0-next.1 - - example-app@0.2.93-next.1 - - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 - @backstage/backend-tasks@0.5.19-next.1 - - @backstage/integration@1.9.1-next.1 - @backstage/plugin-adr-backend@0.4.11-next.1 - @backstage/plugin-auth-backend@0.22.0-next.1 - @backstage/plugin-auth-node@0.4.9-next.1 - @backstage/plugin-badges-backend@0.3.11-next.1 - @backstage/plugin-catalog-backend@1.18.0-next.1 - - @backstage/plugin-code-coverage-backend@0.2.28-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.7-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.31-next.1 - @backstage/plugin-devtools-backend@0.3.0-next.1 - - @backstage/plugin-events-backend@0.3.0-next.1 - - @backstage/plugin-explore-backend@0.0.24-next.1 - @backstage/plugin-jenkins-backend@0.4.0-next.1 - - @backstage/plugin-kafka-backend@0.3.12-next.1 - @backstage/plugin-lighthouse-backend@0.4.6-next.1 - @backstage/plugin-linguist-backend@0.5.11-next.1 - @backstage/plugin-nomad-backend@0.1.16-next.1 @@ -558,103 +465,78 @@ - @backstage/plugin-permission-node@0.7.25-next.1 - @backstage/plugin-playlist-backend@0.3.18-next.1 - @backstage/plugin-proxy-backend@0.4.12-next.1 - - @backstage/plugin-rollbar-backend@0.1.59-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.15-next.1 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.17-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.31-next.1 - @backstage/plugin-search-backend@1.5.4-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.18-next.1 - @backstage/plugin-search-backend-module-explore@0.1.18-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.23-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.18-next.1 - @backstage/plugin-search-backend-node@1.2.18-next.1 - - @backstage/plugin-tech-insights-backend@0.5.28-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.46-next.1 - - @backstage/plugin-tech-insights-node@0.5.0-next.1 + - @backstage/plugin-sonarqube-backend@0.2.16-next.1 - @backstage/plugin-techdocs-backend@1.9.7-next.1 - @backstage/plugin-todo-backend@0.3.12-next.1 - - @backstage/catalog-client@1.6.1-next.0 + - @backstage/backend-defaults@0.2.14-next.1 - @backstage/catalog-model@1.4.5-next.0 - - @backstage/plugin-azure-sites-common@0.1.3-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.11-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.11-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.11-next.1 - - @backstage/plugin-catalog-node@1.8.0-next.1 - - @backstage/plugin-events-node@0.3.0-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.11-next.1 -## 0.2.93-next.0 +## 0.0.21-next.0 ### Patch Changes - Updated dependencies - - @backstage/plugin-events-backend@0.3.0-next.0 - - @backstage/plugin-events-node@0.3.0-next.0 - @backstage/plugin-linguist-backend@0.5.10-next.0 - - @backstage/backend-common@0.21.3-next.0 - @backstage/plugin-auth-node@0.4.8-next.0 - @backstage/plugin-lighthouse-backend@0.4.5-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.16-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.22-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.0-next.0 - @backstage/plugin-playlist-backend@0.3.17-next.0 - - @backstage/plugin-code-coverage-backend@0.2.27-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 - @backstage/plugin-entity-feedback-backend@0.2.10-next.0 + - @backstage/plugin-notifications-backend@0.1.0-next.0 - @backstage/plugin-catalog-backend@1.18.0-next.0 - @backstage/plugin-auth-backend@0.22.0-next.0 - @backstage/plugin-jenkins-backend@0.4.0-next.0 - @backstage/plugin-azure-devops-backend@0.6.0-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.14-next.0 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.16-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.30-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.3-next.0 - @backstage/plugin-scaffolder-backend@1.22.0-next.0 - @backstage/plugin-permission-common@0.7.13-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.17-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.17-next.0 - @backstage/plugin-search-backend-module-explore@0.1.17-next.0 - - @backstage/plugin-catalog-node@1.8.0-next.0 + - @backstage/backend-defaults@0.2.13-next.0 - @backstage/plugin-kubernetes-backend@0.16.0-next.0 - @backstage/plugin-adr-backend@0.4.10-next.0 - @backstage/plugin-proxy-backend@0.4.11-next.0 - @backstage/backend-tasks@0.5.18-next.0 - @backstage/plugin-search-backend-node@1.2.17-next.0 - @backstage/plugin-signals-backend@0.0.4-next.0 - - @backstage/plugin-signals-node@0.0.4-next.0 - - @backstage/plugin-tech-insights-backend@0.5.27-next.0 - @backstage/plugin-search-backend@1.5.3-next.0 - @backstage/plugin-devtools-backend@0.3.0-next.0 - @backstage/plugin-permission-node@0.7.24-next.0 - - @backstage/plugin-tech-insights-node@0.5.0-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.6-next.0 - @backstage/plugin-badges-backend@0.3.10-next.0 - @backstage/plugin-permission-backend@0.5.36-next.0 - @backstage/plugin-app-backend@0.3.61-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.10-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.30-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.10-next.0 - - @backstage/plugin-explore-backend@0.0.23-next.0 - - @backstage/plugin-rollbar-backend@0.1.58-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.45-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.10-next.0 + - @backstage/plugin-sonarqube-backend@0.2.15-next.0 - @backstage/plugin-techdocs-backend@1.9.6-next.0 - - @backstage/plugin-kafka-backend@0.3.11-next.0 - @backstage/plugin-nomad-backend@0.1.15-next.0 - @backstage/plugin-todo-backend@0.3.11-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.10-next.0 - - example-app@0.2.93-next.0 - - @backstage/catalog-client@1.6.1-next.0 - @backstage/catalog-model@1.4.5-next.0 - - @backstage/config@1.1.2-next.0 - - @backstage/integration@1.9.1-next.0 - - @backstage/plugin-azure-sites-common@0.1.3-next.0 -## 0.2.92 +## 0.0.20 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.21.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.27 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7 - @backstage/plugin-scaffolder-backend@1.21.0 - @backstage/plugin-badges-backend@0.3.7 - @backstage/plugin-azure-devops-backend@0.5.2 - - @backstage/plugin-explore-backend@0.0.20 - - @backstage/plugin-auth-backend@0.21.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42 - @backstage/plugin-auth-node@0.4.4 - @backstage/plugin-entity-feedback-backend@0.2.7 - @backstage/plugin-lighthouse-backend@0.4.2 @@ -662,75 +544,56 @@ - @backstage/plugin-linguist-backend@0.5.7 - @backstage/plugin-adr-backend@0.4.7 - @backstage/plugin-kubernetes-backend@0.15.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.13 - @backstage/plugin-signals-backend@0.0.1 - - @backstage/plugin-signals-node@0.0.1 - - @backstage/plugin-tech-insights-backend@0.5.24 - - @backstage/plugin-tech-insights-node@0.4.16 + - @backstage/plugin-notifications-backend@0.0.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.27 - @backstage/plugin-search-backend-module-techdocs@0.1.14 - @backstage/plugin-search-backend-module-catalog@0.1.14 - @backstage/plugin-search-backend-module-explore@0.1.14 - - @backstage/plugin-code-coverage-backend@0.2.24 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/backend-defaults@0.2.10 + - @backstage/plugin-sonarqube-backend@0.2.12 - @backstage/plugin-playlist-backend@0.3.14 - @backstage/plugin-catalog-backend@1.17.0 - @backstage/plugin-jenkins-backend@0.3.4 - - @backstage/plugin-rollbar-backend@0.1.55 - @backstage/backend-tasks@0.5.15 - - @backstage/plugin-events-backend@0.2.19 - @backstage/plugin-nomad-backend@0.1.12 - @backstage/plugin-app-backend@0.3.58 - - @backstage/catalog-model@1.4.4 - - @backstage/integration@1.9.0 - - @backstage/catalog-client@1.6.0 - @backstage/plugin-search-backend@1.5.0 - @backstage/plugin-todo-backend@0.3.8 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3 - @backstage/plugin-techdocs-backend@1.9.3 - - @backstage/plugin-catalog-node@1.7.0 - - @backstage/plugin-azure-sites-common@0.1.2 - - example-app@0.2.92 - - @backstage/plugin-kafka-backend@0.3.8 - @backstage/plugin-permission-backend@0.5.33 - @backstage/plugin-permission-node@0.7.21 - @backstage/plugin-proxy-backend@0.4.8 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.13 - - @backstage/plugin-search-backend-module-pg@0.5.19 - @backstage/plugin-search-backend-node@1.2.14 - - @backstage/config@1.1.1 - - @backstage/plugin-events-node@0.2.19 - @backstage/plugin-permission-common@0.7.12 - - @backstage/plugin-search-common@1.2.10 -## 0.2.92-next.3 +## 0.0.20-next.3 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.21.0-next.3 - @backstage/plugin-badges-backend@0.3.7-next.3 - @backstage/plugin-kubernetes-backend@0.15.0-next.3 - - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.13-next.3 - - @backstage/integration@1.9.0-next.1 - @backstage/backend-tasks@0.5.15-next.3 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.3 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.3 + - @backstage/plugin-notifications-backend@0.0.1-next.1 - @backstage/plugin-signals-backend@0.0.1-next.3 - - @backstage/plugin-signals-node@0.0.1-next.3 - @backstage/plugin-catalog-backend@1.17.0-next.3 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.3 - @backstage/plugin-app-backend@0.3.58-next.3 - - @backstage/plugin-auth-backend@0.21.0-next.3 - - @backstage/plugin-catalog-node@1.6.2-next.3 + - @backstage/backend-defaults@0.2.10-next.3 - @backstage/plugin-adr-backend@0.4.7-next.3 - @backstage/plugin-auth-node@0.4.4-next.3 - @backstage/plugin-azure-devops-backend@0.5.2-next.3 - - @backstage/plugin-code-coverage-backend@0.2.24-next.3 + - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.3 - @backstage/plugin-devtools-backend@0.2.7-next.3 - @backstage/plugin-entity-feedback-backend@0.2.7-next.3 - - @backstage/plugin-events-backend@0.2.19-next.3 - - @backstage/plugin-explore-backend@0.0.20-next.3 - @backstage/plugin-jenkins-backend@0.3.4-next.3 - - @backstage/plugin-kafka-backend@0.3.8-next.3 - @backstage/plugin-lighthouse-backend@0.4.2-next.3 - @backstage/plugin-linguist-backend@0.5.7-next.3 - @backstage/plugin-nomad-backend@0.1.12-next.3 @@ -738,243 +601,170 @@ - @backstage/plugin-permission-node@0.7.21-next.3 - @backstage/plugin-playlist-backend@0.3.14-next.3 - @backstage/plugin-proxy-backend@0.4.8-next.3 - - @backstage/plugin-rollbar-backend@0.1.55-next.3 - @backstage/plugin-scaffolder-backend@1.21.0-next.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.3 - @backstage/plugin-search-backend@1.5.0-next.3 - @backstage/plugin-search-backend-module-catalog@0.1.14-next.3 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.3 - @backstage/plugin-search-backend-module-explore@0.1.14-next.3 - - @backstage/plugin-search-backend-module-pg@0.5.19-next.3 - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.3 - @backstage/plugin-search-backend-node@1.2.14-next.3 - - @backstage/plugin-tech-insights-backend@0.5.24-next.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.3 - - @backstage/plugin-tech-insights-node@0.4.16-next.3 + - @backstage/plugin-sonarqube-backend@0.2.12-next.3 - @backstage/plugin-techdocs-backend@1.9.3-next.3 - @backstage/plugin-todo-backend@0.3.8-next.3 - - example-app@0.2.92-next.3 - - @backstage/catalog-client@1.6.0-next.1 - - @backstage/catalog-model@1.4.4-next.0 - - @backstage/config@1.1.1 - - @backstage/plugin-azure-sites-common@0.1.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.3 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.3 - - @backstage/plugin-events-node@0.2.19-next.3 - @backstage/plugin-permission-common@0.7.12 - - @backstage/plugin-search-common@1.2.10 -## 0.2.92-next.2 +## 0.0.20-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.21.0-next.2 - - @backstage/plugin-auth-backend@0.21.0-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.2 - - @backstage/backend-common@0.21.0-next.2 - @backstage/plugin-signals-backend@0.0.1-next.2 - - @backstage/plugin-signals-node@0.0.1-next.2 - @backstage/plugin-kubernetes-backend@0.15.0-next.2 - - @backstage/plugin-tech-insights-backend@0.5.24-next.2 - - @backstage/plugin-tech-insights-node@0.4.16-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.2 - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.2 - @backstage/plugin-search-backend-module-catalog@0.1.14-next.2 - @backstage/plugin-search-backend-module-explore@0.1.14-next.2 - @backstage/plugin-entity-feedback-backend@0.2.7-next.2 - - @backstage/plugin-code-coverage-backend@0.2.24-next.2 - @backstage/plugin-azure-devops-backend@0.5.2-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 - @backstage/plugin-lighthouse-backend@0.4.2-next.2 + - @backstage/backend-defaults@0.2.10-next.2 + - @backstage/plugin-sonarqube-backend@0.2.12-next.2 - @backstage/plugin-devtools-backend@0.2.7-next.2 - @backstage/plugin-linguist-backend@0.5.7-next.2 - @backstage/plugin-playlist-backend@0.3.14-next.2 - @backstage/plugin-catalog-backend@1.17.0-next.2 - - @backstage/plugin-explore-backend@0.0.20-next.2 - @backstage/plugin-jenkins-backend@0.3.4-next.2 - - @backstage/plugin-rollbar-backend@0.1.55-next.2 - @backstage/backend-tasks@0.5.15-next.2 - @backstage/plugin-badges-backend@0.3.7-next.2 - - @backstage/plugin-events-backend@0.2.19-next.2 - @backstage/plugin-nomad-backend@0.1.12-next.2 - @backstage/plugin-adr-backend@0.4.7-next.2 - @backstage/plugin-app-backend@0.3.58-next.2 - @backstage/plugin-auth-node@0.4.4-next.2 - - example-app@0.2.92-next.2 + - @backstage/plugin-notifications-backend@0.0.1-next.0 - @backstage/plugin-todo-backend@0.3.8-next.2 - - @backstage/plugin-kafka-backend@0.3.8-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.2 - @backstage/plugin-permission-backend@0.5.33-next.2 - @backstage/plugin-permission-node@0.7.21-next.2 - @backstage/plugin-proxy-backend@0.4.8-next.2 - @backstage/plugin-search-backend@1.5.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.19-next.2 - @backstage/plugin-search-backend-node@1.2.14-next.2 - @backstage/plugin-techdocs-backend@1.9.3-next.2 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.2 - - @backstage/plugin-catalog-node@1.6.2-next.2 - - @backstage/plugin-events-node@0.2.19-next.2 - - @backstage/config@1.1.1 - - @backstage/catalog-client@1.6.0-next.1 - - @backstage/catalog-model@1.4.4-next.0 - - @backstage/integration@1.9.0-next.0 - - @backstage/plugin-azure-sites-common@0.1.2-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.2 - @backstage/plugin-permission-common@0.7.12 - - @backstage/plugin-search-common@1.2.10 -## 0.2.92-next.1 +## 0.0.20-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.21.0-next.1 - @backstage/plugin-azure-devops-backend@0.5.2-next.1 - - @backstage/catalog-model@1.4.4-next.0 - - @backstage/catalog-client@1.6.0-next.1 - @backstage/plugin-catalog-backend@1.17.0-next.1 - - @backstage/backend-common@0.21.0-next.1 - - @backstage/plugin-auth-backend@0.20.4-next.1 - - @backstage/integration@1.9.0-next.0 - - @backstage/plugin-azure-sites-common@0.1.2-next.0 - - example-app@0.2.92-next.1 + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-defaults@0.2.10-next.1 - @backstage/backend-tasks@0.5.15-next.1 - - @backstage/config@1.1.1 - @backstage/plugin-adr-backend@0.4.7-next.1 - @backstage/plugin-app-backend@0.3.58-next.1 - @backstage/plugin-auth-node@0.4.4-next.1 - @backstage/plugin-badges-backend@0.3.7-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.1 - - @backstage/plugin-catalog-node@1.6.2-next.1 - - @backstage/plugin-code-coverage-backend@0.2.24-next.1 - @backstage/plugin-devtools-backend@0.2.7-next.1 - @backstage/plugin-entity-feedback-backend@0.2.7-next.1 - - @backstage/plugin-events-backend@0.2.19-next.1 - - @backstage/plugin-events-node@0.2.19-next.1 - - @backstage/plugin-explore-backend@0.0.20-next.1 - @backstage/plugin-jenkins-backend@0.3.4-next.1 - - @backstage/plugin-kafka-backend@0.3.8-next.1 - @backstage/plugin-kubernetes-backend@0.14.2-next.1 - @backstage/plugin-lighthouse-backend@0.4.2-next.1 - @backstage/plugin-linguist-backend@0.5.7-next.1 - @backstage/plugin-nomad-backend@0.1.12-next.1 - @backstage/plugin-permission-backend@0.5.33-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.1 - @backstage/plugin-permission-common@0.7.12 - @backstage/plugin-permission-node@0.7.21-next.1 - @backstage/plugin-playlist-backend@0.3.14-next.1 - @backstage/plugin-proxy-backend@0.4.8-next.1 - - @backstage/plugin-rollbar-backend@0.1.55-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.1 - @backstage/plugin-search-backend@1.5.0-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.14-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.1 - @backstage/plugin-search-backend-module-explore@0.1.14-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.19-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.1 - @backstage/plugin-search-backend-node@1.2.14-next.1 - - @backstage/plugin-search-common@1.2.10 - - @backstage/plugin-signals-backend@0.0.1-next.1 - - @backstage/plugin-signals-node@0.0.1-next.1 - - @backstage/plugin-tech-insights-backend@0.5.24-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.1 - - @backstage/plugin-tech-insights-node@0.4.16-next.1 + - @backstage/plugin-sonarqube-backend@0.2.12-next.1 - @backstage/plugin-techdocs-backend@1.9.3-next.1 - @backstage/plugin-todo-backend@0.3.8-next.1 -## 0.2.92-next.0 +## 0.0.20-next.0 ### Patch Changes - Updated dependencies - - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.0 - @backstage/plugin-azure-devops-backend@0.5.2-next.0 - - @backstage/plugin-explore-backend@0.0.20-next.0 - - @backstage/plugin-auth-backend@0.20.4-next.0 - - @backstage/backend-common@0.21.0-next.0 - @backstage/plugin-kubernetes-backend@0.14.2-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.0 - @backstage/plugin-catalog-backend@1.17.0-next.0 - @backstage/plugin-search-backend@1.5.0-next.0 - @backstage/plugin-todo-backend@0.3.8-next.0 - - @backstage/catalog-client@1.6.0-next.0 - - @backstage/plugin-signals-backend@0.0.1-next.0 - - @backstage/plugin-signals-node@0.0.1-next.0 - @backstage/plugin-scaffolder-backend@1.21.0-next.0 - @backstage/plugin-app-backend@0.3.58-next.0 - - example-app@0.2.92-next.0 + - @backstage/backend-defaults@0.2.10-next.0 - @backstage/backend-tasks@0.5.15-next.0 - @backstage/plugin-auth-node@0.4.4-next.0 - @backstage/plugin-badges-backend@0.3.7-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 - - @backstage/plugin-catalog-node@1.6.2-next.0 - @backstage/plugin-entity-feedback-backend@0.2.7-next.0 - - @backstage/plugin-events-backend@0.2.19-next.0 - @backstage/plugin-linguist-backend@0.5.7-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.0 - @backstage/plugin-permission-node@0.7.21-next.0 - @backstage/plugin-playlist-backend@0.3.14-next.0 - @backstage/plugin-proxy-backend@0.4.8-next.0 - - @backstage/plugin-rollbar-backend@0.1.55-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.19-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 - - @backstage/plugin-tech-insights-backend@0.5.24-next.0 + - @backstage/plugin-sonarqube-backend@0.2.12-next.0 - @backstage/plugin-techdocs-backend@1.9.3-next.0 - @backstage/plugin-adr-backend@0.4.7-next.0 - - @backstage/plugin-azure-sites-backend@0.1.20-next.0 - - @backstage/plugin-code-coverage-backend@0.2.24-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.0 - @backstage/plugin-devtools-backend@0.2.7-next.0 - @backstage/plugin-jenkins-backend@0.3.4-next.0 - - @backstage/plugin-kafka-backend@0.3.8-next.0 - @backstage/plugin-lighthouse-backend@0.4.2-next.0 - @backstage/plugin-nomad-backend@0.1.12-next.0 - @backstage/plugin-permission-backend@0.5.33-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.0 - @backstage/plugin-search-backend-node@1.2.14-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.0 - - @backstage/plugin-tech-insights-node@0.4.16-next.0 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.10-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 - - @backstage/plugin-events-node@0.2.19-next.0 - @backstage/plugin-permission-common@0.7.12 - - @backstage/plugin-search-common@1.2.10 -## 0.2.91 +## 0.0.19 ### Patch Changes - Updated dependencies - - @backstage/plugin-auth-backend@0.20.3 - - @backstage/backend-common@0.20.1 + - @backstage/plugin-sonarqube-backend@0.2.11 - @backstage/plugin-scaffolder-backend@1.20.0 - - @backstage/catalog-client@1.5.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10 - - @backstage/plugin-events-backend@0.2.18 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26 - @backstage/plugin-search-backend-module-techdocs@0.1.13 - @backstage/plugin-search-backend-module-catalog@0.1.13 - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-defaults@0.2.9 - @backstage/plugin-azure-devops-backend@0.5.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2 - @backstage/plugin-entity-feedback-backend@0.2.6 - - @backstage/plugin-code-coverage-backend@0.2.23 - - @backstage/plugin-azure-sites-backend@0.1.19 - - @backstage/plugin-tech-insights-node@0.4.15 - @backstage/plugin-devtools-backend@0.2.6 - @backstage/plugin-linguist-backend@0.5.6 - @backstage/plugin-playlist-backend@0.3.13 - @backstage/plugin-techdocs-backend@1.9.2 - - @backstage/plugin-explore-backend@0.0.19 - @backstage/plugin-jenkins-backend@0.3.3 - @backstage/plugin-badges-backend@0.3.6 - @backstage/plugin-search-backend@1.4.9 - - @backstage/plugin-kafka-backend@0.3.7 - @backstage/plugin-nomad-backend@0.1.11 - - @backstage/plugin-catalog-node@1.6.1 - @backstage/plugin-todo-backend@0.3.7 - @backstage/plugin-adr-backend@0.4.6 - @backstage/plugin-app-backend@0.3.57 @@ -982,211 +772,149 @@ - @backstage/plugin-permission-common@0.7.12 - @backstage/plugin-permission-node@0.7.20 - @backstage/plugin-catalog-backend@1.16.1 - - example-app@0.2.91 - @backstage/backend-tasks@0.5.14 - @backstage/plugin-auth-node@0.4.3 - @backstage/plugin-kubernetes-backend@0.14.1 - @backstage/plugin-lighthouse-backend@0.4.1 - @backstage/plugin-proxy-backend@0.4.7 - - @backstage/plugin-rollbar-backend@0.1.54 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.26 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.12 - - @backstage/plugin-search-backend-module-pg@0.5.18 - @backstage/plugin-search-backend-node@1.2.13 - - @backstage/plugin-tech-insights-backend@0.5.23 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - - @backstage/integration@1.8.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 - - @backstage/plugin-events-node@0.2.18 - - @backstage/plugin-search-common@1.2.10 -## 0.2.91-next.2 +## 0.0.19-next.2 ### Patch Changes - Updated dependencies - - example-app@0.2.91-next.2 - - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-sonarqube-backend@0.2.11-next.2 + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-defaults@0.2.9-next.2 - @backstage/plugin-adr-backend@0.4.6-next.2 - @backstage/plugin-app-backend@0.3.57-next.2 - - @backstage/plugin-auth-backend@0.20.3-next.2 - @backstage/plugin-auth-node@0.4.3-next.2 - @backstage/plugin-azure-devops-backend@0.5.1-next.2 - @backstage/plugin-badges-backend@0.3.6-next.2 - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.2 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 - - @backstage/plugin-catalog-node@1.6.1-next.2 - - @backstage/plugin-code-coverage-backend@0.2.23-next.2 - @backstage/plugin-devtools-backend@0.2.6-next.2 - @backstage/plugin-entity-feedback-backend@0.2.6-next.2 - - @backstage/plugin-events-backend@0.2.18-next.2 - - @backstage/plugin-events-node@0.2.18-next.2 - @backstage/plugin-jenkins-backend@0.3.3-next.2 - - @backstage/plugin-kafka-backend@0.3.7-next.2 - @backstage/plugin-kubernetes-backend@0.14.1-next.2 - @backstage/plugin-lighthouse-backend@0.4.1-next.2 - @backstage/plugin-linguist-backend@0.5.6-next.2 - @backstage/plugin-nomad-backend@0.1.11-next.2 - @backstage/plugin-permission-backend@0.5.32-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.2 - @backstage/plugin-permission-node@0.7.20-next.2 - @backstage/plugin-playlist-backend@0.3.13-next.2 - @backstage/plugin-proxy-backend@0.4.7-next.2 - @backstage/plugin-scaffolder-backend@1.19.3-next.2 - @backstage/plugin-search-backend@1.4.9-next.2 - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.2 - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.18-next.2 - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 - @backstage/plugin-search-backend-node@1.2.13-next.2 - @backstage/plugin-techdocs-backend@1.9.2-next.2 - @backstage/plugin-todo-backend@0.3.7-next.2 - @backstage/backend-tasks@0.5.14-next.2 - - @backstage/plugin-azure-sites-backend@0.1.19-next.2 - - @backstage/plugin-explore-backend@0.0.19-next.2 - - @backstage/plugin-rollbar-backend@0.1.54-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.2 - - @backstage/plugin-tech-insights-backend@0.5.23-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.2 - - @backstage/plugin-tech-insights-node@0.4.15-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.2 -## 0.2.91-next.1 +## 0.0.19-next.1 ### Patch Changes - Updated dependencies - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.1 - - example-app@0.2.91-next.1 - - @backstage/backend-common@0.20.1-next.1 - - @backstage/integration@1.8.0 - @backstage/plugin-app-backend@0.3.57-next.1 - @backstage/plugin-devtools-backend@0.2.6-next.1 - @backstage/plugin-proxy-backend@0.4.7-next.1 - - @backstage/config@1.1.1 + - @backstage/backend-defaults@0.2.9-next.1 - @backstage/plugin-kubernetes-backend@0.14.1-next.1 - @backstage/backend-tasks@0.5.14-next.1 - @backstage/plugin-adr-backend@0.4.6-next.1 - - @backstage/plugin-auth-backend@0.20.3-next.1 - @backstage/plugin-auth-node@0.4.3-next.1 - @backstage/plugin-azure-devops-backend@0.5.1-next.1 - - @backstage/plugin-azure-sites-backend@0.1.19-next.1 - @backstage/plugin-badges-backend@0.3.6-next.1 - @backstage/plugin-catalog-backend@1.16.1-next.1 - - @backstage/plugin-code-coverage-backend@0.2.23-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.1 - @backstage/plugin-entity-feedback-backend@0.2.6-next.1 - - @backstage/plugin-events-backend@0.2.18-next.1 - - @backstage/plugin-explore-backend@0.0.19-next.1 - @backstage/plugin-jenkins-backend@0.3.3-next.1 - - @backstage/plugin-kafka-backend@0.3.7-next.1 - @backstage/plugin-lighthouse-backend@0.4.1-next.1 - @backstage/plugin-linguist-backend@0.5.6-next.1 - @backstage/plugin-nomad-backend@0.1.11-next.1 - @backstage/plugin-permission-backend@0.5.32-next.1 - @backstage/plugin-permission-node@0.7.20-next.1 - @backstage/plugin-playlist-backend@0.3.13-next.1 - - @backstage/plugin-rollbar-backend@0.1.54-next.1 - @backstage/plugin-scaffolder-backend@1.19.3-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.1 - @backstage/plugin-search-backend@1.4.9-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.1 - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.18-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 - @backstage/plugin-search-backend-node@1.2.13-next.1 - - @backstage/plugin-tech-insights-backend@0.5.23-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.1 - - @backstage/plugin-tech-insights-node@0.4.15-next.1 + - @backstage/plugin-sonarqube-backend@0.2.11-next.1 - @backstage/plugin-techdocs-backend@1.9.2-next.1 - @backstage/plugin-todo-backend@0.3.7-next.1 - - @backstage/catalog-client@1.5.2-next.0 - - @backstage/catalog-model@1.4.3 + - @backstage/backend-plugin-api@0.6.9-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 - - @backstage/plugin-catalog-node@1.6.1-next.1 - - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.1 - @backstage/plugin-permission-common@0.7.11 - - @backstage/plugin-search-common@1.2.9 -## 0.2.91-next.0 +## 0.0.19-next.0 ### Patch Changes - Updated dependencies - - @backstage/plugin-auth-backend@0.20.3-next.0 - - @backstage/backend-common@0.20.1-next.0 - @backstage/plugin-scaffolder-backend@1.19.3-next.0 - - @backstage/catalog-client@1.5.2-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 - @backstage/plugin-azure-devops-backend@0.5.1-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.0 - @backstage/plugin-entity-feedback-backend@0.2.6-next.0 - - @backstage/plugin-code-coverage-backend@0.2.23-next.0 - - @backstage/plugin-azure-sites-backend@0.1.19-next.0 - - @backstage/plugin-tech-insights-node@0.4.15-next.0 - @backstage/plugin-devtools-backend@0.2.6-next.0 - @backstage/plugin-linguist-backend@0.5.6-next.0 - @backstage/plugin-playlist-backend@0.3.13-next.0 - @backstage/plugin-techdocs-backend@1.9.2-next.0 - - @backstage/plugin-explore-backend@0.0.19-next.0 - @backstage/plugin-jenkins-backend@0.3.3-next.0 - @backstage/plugin-badges-backend@0.3.6-next.0 - @backstage/plugin-search-backend@1.4.9-next.0 - - @backstage/plugin-kafka-backend@0.3.7-next.0 - @backstage/plugin-nomad-backend@0.1.11-next.0 - - @backstage/plugin-catalog-node@1.6.1-next.0 - @backstage/plugin-todo-backend@0.3.7-next.0 - @backstage/plugin-adr-backend@0.4.6-next.0 - @backstage/plugin-app-backend@0.3.57-next.0 - - example-app@0.2.91-next.0 + - @backstage/backend-defaults@0.2.9-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 - @backstage/backend-tasks@0.5.14-next.0 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - - @backstage/integration@1.8.0 - @backstage/plugin-auth-node@0.4.3-next.0 - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 - - @backstage/plugin-events-backend@0.2.18-next.0 - - @backstage/plugin-events-node@0.2.18-next.0 - @backstage/plugin-kubernetes-backend@0.14.1-next.0 - @backstage/plugin-lighthouse-backend@0.4.1-next.0 - @backstage/plugin-permission-backend@0.5.32-next.0 - @backstage/plugin-permission-common@0.7.11 - @backstage/plugin-permission-node@0.7.20-next.0 - @backstage/plugin-proxy-backend@0.4.7-next.0 - - @backstage/plugin-rollbar-backend@0.1.54-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.18-next.0 - @backstage/plugin-search-backend-node@1.2.13-next.0 - - @backstage/plugin-search-common@1.2.9 - - @backstage/plugin-tech-insights-backend@0.5.23-next.0 + - @backstage/plugin-sonarqube-backend@0.2.11-next.0 -## 0.2.90 +## 0.0.18 ### Patch Changes - Updated dependencies - - @backstage/plugin-auth-backend@0.20.1 - - @backstage/backend-common@0.20.0 - - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1 - @backstage/plugin-techdocs-backend@1.9.1 - @backstage/plugin-catalog-backend@1.16.0 - - @backstage/catalog-client@1.5.0 - @backstage/plugin-azure-devops-backend@0.5.0 - @backstage/plugin-scaffolder-backend@1.19.2 - @backstage/backend-tasks@0.5.13 - @backstage/plugin-lighthouse-backend@0.4.0 - @backstage/plugin-kubernetes-backend@0.14.0 - - @backstage/integration@1.8.0 - - @backstage/plugin-azure-sites-backend@0.1.18 - @backstage/plugin-auth-node@0.4.2 - @backstage/plugin-permission-backend@0.5.31 - @backstage/plugin-permission-common@0.7.11 @@ -1194,134 +922,94 @@ - @backstage/plugin-permission-node@0.7.19 - @backstage/plugin-search-backend@1.4.8 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.11 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 - @backstage/plugin-search-backend-module-techdocs@0.1.12 - @backstage/plugin-search-backend-module-catalog@0.1.12 - @backstage/plugin-search-backend-module-explore@0.1.12 - - @backstage/plugin-search-backend-module-pg@0.5.17 - - @backstage/plugin-events-backend@0.2.17 - - example-app@0.2.90 + - @backstage/backend-defaults@0.2.8 - @backstage/plugin-adr-backend@0.4.5 - @backstage/plugin-app-backend@0.3.56 - @backstage/plugin-badges-backend@0.3.5 - - @backstage/plugin-code-coverage-backend@0.2.22 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25 - @backstage/plugin-devtools-backend@0.2.5 - @backstage/plugin-entity-feedback-backend@0.2.5 - - @backstage/plugin-explore-backend@0.0.18 - @backstage/plugin-jenkins-backend@0.3.2 - - @backstage/plugin-kafka-backend@0.3.6 - @backstage/plugin-linguist-backend@0.5.5 - @backstage/plugin-nomad-backend@0.1.10 - @backstage/plugin-proxy-backend@0.4.6 - - @backstage/plugin-rollbar-backend@0.1.53 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.25 - @backstage/plugin-search-backend-node@1.2.12 - - @backstage/plugin-tech-insights-backend@0.5.22 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40 - - @backstage/plugin-tech-insights-node@0.4.14 + - @backstage/plugin-sonarqube-backend@0.2.10 - @backstage/plugin-todo-backend@0.3.6 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - - @backstage/plugin-events-node@0.2.17 - - @backstage/plugin-search-common@1.2.9 + - @backstage/backend-plugin-api@0.6.8 -## 0.2.90-next.3 +## 0.0.18-next.3 ### Patch Changes - Updated dependencies - @backstage/plugin-azure-devops-backend@0.5.0-next.3 - @backstage/plugin-scaffolder-backend@1.19.2-next.3 - - @backstage/backend-common@0.20.0-next.3 - - example-app@0.2.90-next.4 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.3 + - @backstage/backend-defaults@0.2.8-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 - @backstage/backend-tasks@0.5.13-next.3 - - @backstage/catalog-client@1.5.0-next.1 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - - @backstage/integration@1.8.0-next.1 - @backstage/plugin-adr-backend@0.4.5-next.3 - @backstage/plugin-app-backend@0.3.56-next.3 - - @backstage/plugin-auth-backend@0.20.1-next.3 - @backstage/plugin-auth-node@0.4.2-next.3 - - @backstage/plugin-azure-sites-backend@0.1.18-next.3 - @backstage/plugin-badges-backend@0.3.5-next.3 - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.3 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.3 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 - - @backstage/plugin-catalog-node@1.6.0-next.3 - - @backstage/plugin-code-coverage-backend@0.2.22-next.3 - @backstage/plugin-devtools-backend@0.2.5-next.3 - @backstage/plugin-entity-feedback-backend@0.2.5-next.3 - - @backstage/plugin-events-backend@0.2.17-next.3 - - @backstage/plugin-events-node@0.2.17-next.3 - - @backstage/plugin-explore-backend@0.0.18-next.3 - @backstage/plugin-jenkins-backend@0.3.2-next.3 - - @backstage/plugin-kafka-backend@0.3.6-next.3 - @backstage/plugin-kubernetes-backend@0.14.0-next.3 - @backstage/plugin-lighthouse-backend@0.4.0-next.3 - @backstage/plugin-linguist-backend@0.5.5-next.3 - @backstage/plugin-nomad-backend@0.1.10-next.3 - @backstage/plugin-permission-backend@0.5.31-next.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.3 - @backstage/plugin-permission-common@0.7.10 - @backstage/plugin-permission-node@0.7.19-next.3 - @backstage/plugin-playlist-backend@0.3.12-next.3 - @backstage/plugin-proxy-backend@0.4.6-next.3 - - @backstage/plugin-rollbar-backend@0.1.53-next.3 - @backstage/plugin-search-backend@1.4.8-next.3 - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.3 - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 - - @backstage/plugin-search-backend-module-pg@0.5.17-next.3 - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 - @backstage/plugin-search-backend-node@1.2.12-next.3 - - @backstage/plugin-search-common@1.2.8 - - @backstage/plugin-tech-insights-backend@0.5.22-next.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.3 - - @backstage/plugin-tech-insights-node@0.4.14-next.3 + - @backstage/plugin-sonarqube-backend@0.2.10-next.3 - @backstage/plugin-techdocs-backend@1.9.1-next.3 - @backstage/plugin-todo-backend@0.3.6-next.3 -## 0.2.90-next.2 +## 0.0.18-next.2 ### Patch Changes - Updated dependencies - - @backstage/plugin-catalog-node@1.6.0-next.2 - @backstage/plugin-catalog-backend@1.16.0-next.2 - - @backstage/plugin-auth-backend@0.20.1-next.2 - @backstage/plugin-lighthouse-backend@0.4.0-next.2 - - @backstage/backend-common@0.20.0-next.2 - @backstage/plugin-auth-node@0.4.2-next.2 - - @backstage/catalog-client@1.5.0-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.17-next.2 - - @backstage/plugin-events-backend@0.2.17-next.2 - - example-app@0.2.90-next.3 + - @backstage/backend-defaults@0.2.8-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 - @backstage/backend-tasks@0.5.13-next.2 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - - @backstage/integration@1.8.0-next.1 - @backstage/plugin-adr-backend@0.4.5-next.2 - @backstage/plugin-app-backend@0.3.56-next.2 - @backstage/plugin-azure-devops-backend@0.5.0-next.2 - - @backstage/plugin-azure-sites-backend@0.1.18-next.2 - @backstage/plugin-badges-backend@0.3.5-next.2 - - @backstage/plugin-code-coverage-backend@0.2.22-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.2 - @backstage/plugin-devtools-backend@0.2.5-next.2 - @backstage/plugin-entity-feedback-backend@0.2.5-next.2 - - @backstage/plugin-events-node@0.2.17-next.2 - - @backstage/plugin-explore-backend@0.0.18-next.2 - @backstage/plugin-jenkins-backend@0.3.2-next.2 - - @backstage/plugin-kafka-backend@0.3.6-next.2 - @backstage/plugin-kubernetes-backend@0.14.0-next.2 - @backstage/plugin-linguist-backend@0.5.5-next.2 - @backstage/plugin-nomad-backend@0.1.10-next.2 @@ -1330,230 +1018,165 @@ - @backstage/plugin-permission-node@0.7.19-next.2 - @backstage/plugin-playlist-backend@0.3.12-next.2 - @backstage/plugin-proxy-backend@0.4.6-next.2 - - @backstage/plugin-rollbar-backend@0.1.53-next.2 - @backstage/plugin-scaffolder-backend@1.19.2-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.2 - @backstage/plugin-search-backend@1.4.8-next.2 - @backstage/plugin-search-backend-node@1.2.12-next.2 - - @backstage/plugin-search-common@1.2.8 - - @backstage/plugin-tech-insights-backend@0.5.22-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.2 - - @backstage/plugin-tech-insights-node@0.4.14-next.2 + - @backstage/plugin-sonarqube-backend@0.2.10-next.2 - @backstage/plugin-techdocs-backend@1.9.1-next.2 - @backstage/plugin-todo-backend@0.3.6-next.2 -## 0.2.90-next.1 +## 0.0.18-next.1 ### Patch Changes - Updated dependencies - - @backstage/plugin-auth-backend@0.20.1-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.1 - @backstage/plugin-catalog-backend@1.15.1-next.1 - - @backstage/catalog-client@1.5.0-next.0 - @backstage/plugin-azure-devops-backend@0.5.0-next.1 - @backstage/plugin-kubernetes-backend@0.14.0-next.1 - - @backstage/integration@1.8.0-next.1 - - @backstage/plugin-azure-sites-backend@0.1.18-next.1 - - @backstage/backend-common@0.20.0-next.1 - - example-app@0.2.90-next.2 + - @backstage/backend-defaults@0.2.8-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 - @backstage/backend-tasks@0.5.13-next.1 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - @backstage/plugin-adr-backend@0.4.5-next.1 - @backstage/plugin-app-backend@0.3.56-next.1 - @backstage/plugin-auth-node@0.4.2-next.1 - @backstage/plugin-badges-backend@0.3.5-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 - - @backstage/plugin-catalog-node@1.5.1-next.1 - - @backstage/plugin-code-coverage-backend@0.2.22-next.1 - @backstage/plugin-devtools-backend@0.2.5-next.1 - @backstage/plugin-entity-feedback-backend@0.2.5-next.1 - - @backstage/plugin-events-backend@0.2.17-next.1 - - @backstage/plugin-events-node@0.2.17-next.1 - - @backstage/plugin-explore-backend@0.0.18-next.1 - @backstage/plugin-jenkins-backend@0.3.2-next.1 - - @backstage/plugin-kafka-backend@0.3.6-next.1 - @backstage/plugin-lighthouse-backend@0.3.5-next.1 - @backstage/plugin-linguist-backend@0.5.5-next.1 - @backstage/plugin-nomad-backend@0.1.10-next.1 - @backstage/plugin-permission-backend@0.5.31-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.1 - @backstage/plugin-permission-common@0.7.10 - @backstage/plugin-permission-node@0.7.19-next.1 - @backstage/plugin-playlist-backend@0.3.12-next.1 - @backstage/plugin-proxy-backend@0.4.6-next.1 - - @backstage/plugin-rollbar-backend@0.1.53-next.1 - @backstage/plugin-scaffolder-backend@1.19.2-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.1 - @backstage/plugin-search-backend@1.4.8-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.1 - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.17-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 - @backstage/plugin-search-backend-node@1.2.12-next.1 - - @backstage/plugin-search-common@1.2.8 - - @backstage/plugin-tech-insights-backend@0.5.22-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.1 - - @backstage/plugin-tech-insights-node@0.4.14-next.1 + - @backstage/plugin-sonarqube-backend@0.2.10-next.1 - @backstage/plugin-techdocs-backend@1.9.1-next.1 - @backstage/plugin-todo-backend@0.3.6-next.1 -## 0.2.90-next.0 +## 0.0.18-next.0 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.20.0-next.0 - - @backstage/plugin-auth-backend@0.20.1-next.0 - @backstage/backend-tasks@0.5.13-next.0 - @backstage/plugin-scaffolder-backend@1.19.2-next.0 - @backstage/plugin-kubernetes-backend@0.14.0-next.0 - - @backstage/plugin-azure-sites-backend@0.1.18-next.0 - - @backstage/integration@1.8.0-next.0 - - example-app@0.2.90-next.0 + - @backstage/backend-defaults@0.2.8-next.0 - @backstage/plugin-adr-backend@0.4.5-next.0 - @backstage/plugin-app-backend@0.3.56-next.0 - @backstage/plugin-auth-node@0.4.2-next.0 - @backstage/plugin-azure-devops-backend@0.4.5-next.0 - @backstage/plugin-badges-backend@0.3.5-next.0 - @backstage/plugin-catalog-backend@1.15.1-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.0 - - @backstage/plugin-catalog-node@1.5.1-next.0 - - @backstage/plugin-code-coverage-backend@0.2.22-next.0 - @backstage/plugin-devtools-backend@0.2.5-next.0 - @backstage/plugin-entity-feedback-backend@0.2.5-next.0 - - @backstage/plugin-events-backend@0.2.17-next.0 - - @backstage/plugin-explore-backend@0.0.18-next.0 - @backstage/plugin-jenkins-backend@0.3.2-next.0 - - @backstage/plugin-kafka-backend@0.3.6-next.0 - @backstage/plugin-lighthouse-backend@0.3.5-next.0 - @backstage/plugin-linguist-backend@0.5.5-next.0 - @backstage/plugin-nomad-backend@0.1.10-next.0 - @backstage/plugin-permission-backend@0.5.31-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.0 - @backstage/plugin-permission-node@0.7.19-next.0 - @backstage/plugin-playlist-backend@0.3.12-next.0 - @backstage/plugin-proxy-backend@0.4.6-next.0 - - @backstage/plugin-rollbar-backend@0.1.53-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.0 - @backstage/plugin-search-backend@1.4.8-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.12-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.0 - @backstage/plugin-search-backend-module-explore@0.1.12-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.17-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.0 - @backstage/plugin-search-backend-node@1.2.12-next.0 - - @backstage/plugin-tech-insights-backend@0.5.22-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.0 - - @backstage/plugin-tech-insights-node@0.4.14-next.0 + - @backstage/plugin-sonarqube-backend@0.2.10-next.0 - @backstage/plugin-techdocs-backend@1.9.1-next.0 - @backstage/plugin-todo-backend@0.3.6-next.0 - - @backstage/catalog-client@1.4.6 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.8-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.0 - - @backstage/plugin-events-node@0.2.17-next.0 - @backstage/plugin-permission-common@0.7.10 - - @backstage/plugin-search-common@1.2.8 -## 0.2.89 +## 0.0.17 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.15.0 - - @backstage/plugin-catalog-node@1.5.0 - - @backstage/plugin-search-backend-module-pg@0.5.16 - @backstage/plugin-kubernetes-backend@0.13.1 - @backstage/plugin-search-backend-node@1.2.11 - - @backstage/integration@1.7.2 - - @backstage/plugin-auth-backend@0.20.0 - - @backstage/backend-common@0.19.9 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0 - @backstage/plugin-techdocs-backend@1.9.0 - - @backstage/plugin-code-coverage-backend@0.2.21 - @backstage/plugin-scaffolder-backend@1.19.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.10 - @backstage/plugin-search-backend@1.4.7 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4 - @backstage/plugin-entity-feedback-backend@0.2.4 - - @backstage/plugin-tech-insights-backend@0.5.21 + - @backstage/backend-plugin-api@0.6.7 - @backstage/plugin-linguist-backend@0.5.4 - @backstage/plugin-playlist-backend@0.3.11 - @backstage/backend-tasks@0.5.12 - @backstage/plugin-badges-backend@0.3.4 - @backstage/plugin-app-backend@0.3.55 - @backstage/plugin-search-backend-module-techdocs@0.1.11 - - @backstage/catalog-client@1.4.6 - @backstage/plugin-permission-common@0.7.10 - @backstage/plugin-jenkins-backend@0.3.1 - @backstage/plugin-adr-backend@0.4.4 - - @backstage/plugin-kafka-backend@0.3.5 - @backstage/plugin-proxy-backend@0.4.5 - - example-app@0.2.89 + - @backstage/plugin-catalog-backend-module-openapi@0.1.24 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4 - @backstage/plugin-lighthouse-backend@0.3.4 - @backstage/plugin-search-backend-module-catalog@0.1.11 - @backstage/plugin-todo-backend@0.3.5 - @backstage/plugin-devtools-backend@0.2.4 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 + - @backstage/backend-defaults@0.2.7 - @backstage/plugin-auth-node@0.4.1 - @backstage/plugin-azure-devops-backend@0.4.4 - - @backstage/plugin-azure-sites-backend@0.1.17 - - @backstage/plugin-events-backend@0.2.16 - - @backstage/plugin-events-node@0.2.16 - - @backstage/plugin-explore-backend@0.0.17 - - @backstage/plugin-graphql-backend@0.2.1 - @backstage/plugin-nomad-backend@0.1.9 - @backstage/plugin-permission-backend@0.5.30 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4 - @backstage/plugin-permission-node@0.7.18 - - @backstage/plugin-rollbar-backend@0.1.52 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.24 - @backstage/plugin-search-backend-module-explore@0.1.11 - - @backstage/plugin-search-common@1.2.8 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39 - - @backstage/plugin-tech-insights-node@0.4.13 + - @backstage/plugin-sonarqube-backend@0.2.9 -## 0.2.89-next.2 +## 0.0.17-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-kubernetes-backend@0.13.1-next.2 - @backstage/plugin-scaffolder-backend@1.19.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.10-next.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.16-next.2 - @backstage/plugin-entity-feedback-backend@0.2.4-next.2 - - @backstage/plugin-code-coverage-backend@0.2.21-next.2 - - @backstage/plugin-tech-insights-backend@0.5.21-next.2 + - @backstage/backend-plugin-api@0.6.7-next.2 - @backstage/plugin-linguist-backend@0.5.4-next.2 - @backstage/plugin-playlist-backend@0.3.11-next.2 - @backstage/plugin-techdocs-backend@1.9.0-next.2 - - @backstage/backend-common@0.19.9-next.2 - @backstage/plugin-catalog-backend@1.15.0-next.2 - @backstage/backend-tasks@0.5.12-next.2 - @backstage/plugin-badges-backend@0.3.4-next.2 - - @backstage/plugin-auth-backend@0.20.0-next.2 - @backstage/plugin-app-backend@0.3.55-next.2 - - example-app@0.2.89-next.2 + - @backstage/backend-defaults@0.2.7-next.2 - @backstage/plugin-adr-backend@0.4.4-next.2 - @backstage/plugin-auth-node@0.4.1-next.2 - @backstage/plugin-azure-devops-backend@0.4.4-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0-next.2 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.2 - - @backstage/plugin-catalog-node@1.5.0-next.2 - @backstage/plugin-devtools-backend@0.2.4-next.2 - - @backstage/plugin-events-backend@0.2.16-next.2 - - @backstage/plugin-events-node@0.2.16-next.2 - @backstage/plugin-jenkins-backend@0.3.1-next.2 - - @backstage/plugin-kafka-backend@0.3.5-next.2 - @backstage/plugin-lighthouse-backend@0.3.4-next.2 - @backstage/plugin-nomad-backend@0.1.9-next.2 - @backstage/plugin-permission-backend@0.5.30-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4-next.2 - @backstage/plugin-permission-node@0.7.18-next.2 - @backstage/plugin-proxy-backend@0.4.5-next.2 - @backstage/plugin-search-backend@1.4.7-next.2 @@ -1561,27 +1184,20 @@ - @backstage/plugin-search-backend-module-explore@0.1.11-next.2 - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.2 - @backstage/plugin-search-backend-node@1.2.11-next.2 + - @backstage/plugin-sonarqube-backend@0.2.9-next.2 - @backstage/plugin-todo-backend@0.3.5-next.2 - - @backstage/plugin-rollbar-backend@0.1.52-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.24-next.2 - - @backstage/plugin-azure-sites-backend@0.1.17-next.2 - - @backstage/plugin-explore-backend@0.0.17-next.2 - - @backstage/plugin-graphql-backend@0.2.1-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39-next.2 - - @backstage/plugin-tech-insights-node@0.4.13-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.24-next.2 -## 0.2.89-next.1 +## 0.0.17-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.15.0-next.1 - - @backstage/plugin-catalog-node@1.5.0-next.1 - - @backstage/integration@1.7.2-next.0 - - @backstage/plugin-auth-backend@0.20.0-next.1 - @backstage/plugin-techdocs-backend@1.9.0-next.1 - @backstage/plugin-scaffolder-backend@1.19.0-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.24-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.1 - @backstage/plugin-jenkins-backend@0.3.1-next.1 - @backstage/plugin-kubernetes-backend@0.13.1-next.1 @@ -1590,351 +1206,230 @@ - @backstage/plugin-search-backend-module-catalog@0.1.11-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.1 - @backstage/plugin-todo-backend@0.3.5-next.1 - - example-app@0.2.89-next.1 - - @backstage/backend-common@0.19.9-next.1 - @backstage/plugin-adr-backend@0.4.4-next.1 - - @backstage/plugin-code-coverage-backend@0.2.21-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.24-next.1 + - @backstage/backend-defaults@0.2.7-next.1 - @backstage/backend-tasks@0.5.12-next.1 - @backstage/plugin-app-backend@0.3.55-next.1 - @backstage/plugin-auth-node@0.4.1-next.1 - @backstage/plugin-badges-backend@0.3.4-next.1 - @backstage/plugin-entity-feedback-backend@0.2.4-next.1 - - @backstage/plugin-events-backend@0.2.16-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4-next.1 - @backstage/plugin-permission-node@0.7.18-next.1 - @backstage/plugin-playlist-backend@0.3.11-next.1 - @backstage/plugin-proxy-backend@0.4.5-next.1 - - @backstage/plugin-rollbar-backend@0.1.52-next.1 - @backstage/plugin-search-backend@1.4.7-next.1 - @backstage/plugin-search-backend-module-explore@0.1.11-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.16-next.1 - - @backstage/plugin-tech-insights-backend@0.5.21-next.1 + - @backstage/plugin-sonarqube-backend@0.2.9-next.1 - @backstage/plugin-azure-devops-backend@0.4.4-next.1 - - @backstage/plugin-azure-sites-backend@0.1.17-next.1 - @backstage/plugin-devtools-backend@0.2.4-next.1 - - @backstage/plugin-explore-backend@0.0.17-next.1 - - @backstage/plugin-graphql-backend@0.2.1-next.1 - - @backstage/plugin-kafka-backend@0.3.5-next.1 - @backstage/plugin-nomad-backend@0.1.9-next.1 - @backstage/plugin-permission-backend@0.5.30-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.10-next.1 - @backstage/plugin-search-backend-node@1.2.11-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39-next.1 - - @backstage/plugin-tech-insights-node@0.4.13-next.1 - - @backstage/catalog-client@1.4.5 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.7-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.1 - - @backstage/plugin-events-node@0.2.16-next.1 - @backstage/plugin-permission-common@0.7.9 - - @backstage/plugin-search-common@1.2.7 -## 0.2.89-next.0 +## 0.0.17-next.0 ### Patch Changes - Updated dependencies - @backstage/plugin-search-backend-node@1.2.11-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.0-next.0 - @backstage/plugin-techdocs-backend@1.8.1-next.0 - - @backstage/plugin-code-coverage-backend@0.2.21-next.0 - @backstage/plugin-scaffolder-backend@1.19.0-next.0 - @backstage/plugin-catalog-backend@1.15.0-next.0 - @backstage/plugin-search-backend@1.4.7-next.0 - - @backstage/plugin-tech-insights-backend@0.5.21-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.11-next.0 - - @backstage/plugin-kafka-backend@0.3.5-next.0 - @backstage/plugin-proxy-backend@0.4.5-next.0 - - @backstage/plugin-auth-backend@0.20.0-next.0 - - @backstage/backend-common@0.19.9-next.0 - - @backstage/integration@1.7.1 - @backstage/plugin-app-backend@0.3.55-next.0 - @backstage/plugin-devtools-backend@0.2.4-next.0 - - example-app@0.2.89-next.0 + - @backstage/backend-defaults@0.2.7-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 - @backstage/backend-tasks@0.5.12-next.0 - - @backstage/catalog-client@1.4.5 - - @backstage/catalog-model@1.4.3 - - @backstage/config@1.1.1 - @backstage/plugin-adr-backend@0.4.4-next.0 - @backstage/plugin-auth-node@0.4.1-next.0 - @backstage/plugin-azure-devops-backend@0.4.4-next.0 - - @backstage/plugin-azure-sites-backend@0.1.17-next.0 - @backstage/plugin-badges-backend@0.3.4-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.24-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.4-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.4-next.0 - - @backstage/plugin-catalog-node@1.4.8-next.0 - @backstage/plugin-entity-feedback-backend@0.2.4-next.0 - - @backstage/plugin-events-backend@0.2.16-next.0 - - @backstage/plugin-events-node@0.2.16-next.0 - - @backstage/plugin-explore-backend@0.0.17-next.0 - - @backstage/plugin-graphql-backend@0.2.1-next.0 - @backstage/plugin-jenkins-backend@0.3.1-next.0 - @backstage/plugin-kubernetes-backend@0.13.1-next.0 - @backstage/plugin-lighthouse-backend@0.3.4-next.0 - @backstage/plugin-linguist-backend@0.5.4-next.0 - @backstage/plugin-nomad-backend@0.1.9-next.0 - @backstage/plugin-permission-backend@0.5.30-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.4-next.0 - @backstage/plugin-permission-common@0.7.9 - @backstage/plugin-permission-node@0.7.18-next.0 - @backstage/plugin-playlist-backend@0.3.11-next.0 - - @backstage/plugin-rollbar-backend@0.1.52-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.8-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.24-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.11-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.10-next.0 - @backstage/plugin-search-backend-module-explore@0.1.11-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.16-next.0 - - @backstage/plugin-search-common@1.2.7 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.39-next.0 - - @backstage/plugin-tech-insights-node@0.4.13-next.0 + - @backstage/plugin-sonarqube-backend@0.2.9-next.0 - @backstage/plugin-todo-backend@0.3.5-next.0 -## 0.2.88 +## 0.0.16 ### Patch Changes - Updated dependencies - @backstage/plugin-nomad-backend@0.1.8 - @backstage/backend-tasks@0.5.11 - - @backstage/backend-common@0.19.8 + - @backstage/plugin-sonarqube-backend@0.2.8 - @backstage/plugin-scaffolder-backend@1.18.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.9 - - @backstage/integration@1.7.1 - @backstage/plugin-playlist-backend@0.3.10 - @backstage/plugin-techdocs-backend@1.8.0 - - @backstage/plugin-auth-backend@0.19.3 - - @backstage/plugin-rollbar-backend@0.1.51 - @backstage/plugin-catalog-backend@1.14.0 - - @backstage/plugin-catalog-node@1.4.7 - @backstage/plugin-auth-node@0.4.0 - - @backstage/plugin-graphql-backend@0.2.0 - - @backstage/catalog-model@1.4.3 - @backstage/plugin-badges-backend@0.3.3 - - @backstage/plugin-tech-insights-backend@0.5.20 - @backstage/plugin-kubernetes-backend@0.13.0 - @backstage/plugin-jenkins-backend@0.3.0 - - @backstage/plugin-code-coverage-backend@0.2.20 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.23 - @backstage/plugin-search-backend@1.4.6 - - example-app@0.2.88 + - @backstage/backend-plugin-api@0.6.6 - @backstage/plugin-lighthouse-backend@0.3.3 - @backstage/plugin-linguist-backend@0.5.3 - @backstage/plugin-search-backend-module-catalog@0.1.10 - @backstage/plugin-search-backend-module-explore@0.1.10 - @backstage/plugin-search-backend-module-techdocs@0.1.10 - @backstage/plugin-search-backend-node@1.2.10 - - @backstage/plugin-tech-insights-node@0.4.12 + - @backstage/backend-defaults@0.2.6 - @backstage/plugin-adr-backend@0.4.3 - @backstage/plugin-app-backend@0.3.54 - @backstage/plugin-azure-devops-backend@0.4.3 - - @backstage/plugin-azure-sites-backend@0.1.16 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.3 - @backstage/plugin-devtools-backend@0.2.3 - @backstage/plugin-entity-feedback-backend@0.2.3 - - @backstage/plugin-events-backend@0.2.15 - - @backstage/plugin-explore-backend@0.0.16 - - @backstage/plugin-kafka-backend@0.3.3 - @backstage/plugin-permission-backend@0.5.29 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.3 - @backstage/plugin-permission-node@0.7.17 - @backstage/plugin-proxy-backend@0.4.3 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.7 - - @backstage/plugin-search-backend-module-pg@0.5.15 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.38 - @backstage/plugin-todo-backend@0.3.4 - - @backstage/catalog-client@1.4.5 - - @backstage/config@1.1.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.3 - - @backstage/plugin-events-node@0.2.15 - @backstage/plugin-permission-common@0.7.9 - - @backstage/plugin-search-common@1.2.7 -## 0.2.88-next.2 +## 0.0.16-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-nomad-backend@0.1.8-next.2 - - @backstage/backend-common@0.19.8-next.2 - @backstage/plugin-scaffolder-backend@1.18.0-next.2 - @backstage/plugin-techdocs-backend@1.8.0-next.2 - @backstage/plugin-auth-node@0.4.0-next.2 - @backstage/plugin-catalog-backend@1.14.0-next.2 - - @backstage/catalog-model@1.4.3-next.0 - - @backstage/integration@1.7.1-next.1 - @backstage/plugin-kubernetes-backend@0.12.3-next.2 - @backstage/plugin-jenkins-backend@0.2.9-next.2 - - @backstage/plugin-auth-backend@0.19.3-next.2 + - @backstage/backend-defaults@0.2.6-next.2 - @backstage/backend-tasks@0.5.11-next.2 - @backstage/plugin-adr-backend@0.4.3-next.2 - @backstage/plugin-app-backend@0.3.54-next.2 - @backstage/plugin-azure-devops-backend@0.4.3-next.2 - - @backstage/plugin-azure-sites-backend@0.1.16-next.2 - @backstage/plugin-badges-backend@0.3.3-next.2 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.3-next.2 - - @backstage/plugin-catalog-node@1.4.7-next.2 - - @backstage/plugin-code-coverage-backend@0.2.20-next.2 - @backstage/plugin-devtools-backend@0.2.3-next.2 - @backstage/plugin-entity-feedback-backend@0.2.3-next.2 - - @backstage/plugin-events-backend@0.2.15-next.2 - - @backstage/plugin-explore-backend@0.0.16-next.2 - - @backstage/plugin-graphql-backend@0.1.44-next.2 - - @backstage/plugin-kafka-backend@0.3.3-next.2 - @backstage/plugin-lighthouse-backend@0.3.3-next.2 - @backstage/plugin-linguist-backend@0.5.3-next.2 - @backstage/plugin-permission-backend@0.5.29-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.3-next.2 - @backstage/plugin-permission-node@0.7.17-next.2 - @backstage/plugin-playlist-backend@0.3.10-next.2 - @backstage/plugin-proxy-backend@0.4.3-next.2 - - @backstage/plugin-rollbar-backend@0.1.51-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.7-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.23-next.2 - @backstage/plugin-search-backend@1.4.6-next.2 - @backstage/plugin-search-backend-module-catalog@0.1.10-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.9-next.2 - @backstage/plugin-search-backend-module-explore@0.1.10-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.15-next.2 - @backstage/plugin-search-backend-module-techdocs@0.1.10-next.2 - @backstage/plugin-search-backend-node@1.2.10-next.2 - - @backstage/plugin-tech-insights-backend@0.5.20-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.38-next.2 - - @backstage/plugin-tech-insights-node@0.4.12-next.2 + - @backstage/plugin-sonarqube-backend@0.2.8-next.2 - @backstage/plugin-todo-backend@0.3.4-next.2 - - example-app@0.2.88-next.2 - - @backstage/catalog-client@1.4.5-next.0 - - @backstage/config@1.1.1-next.0 + - @backstage/backend-plugin-api@0.6.6-next.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.3-next.2 - - @backstage/plugin-events-node@0.2.15-next.2 - @backstage/plugin-permission-common@0.7.9-next.0 - - @backstage/plugin-search-common@1.2.7-next.0 -## 0.2.88-next.1 +## 0.0.16-next.1 ### Patch Changes - Updated dependencies - @backstage/backend-tasks@0.5.10-next.1 - @backstage/plugin-catalog-backend@1.14.0-next.1 - - @backstage/plugin-catalog-node@1.4.6-next.1 - - @backstage/backend-common@0.19.7-next.1 - @backstage/plugin-scaffolder-backend@1.18.0-next.1 - @backstage/plugin-badges-backend@0.3.2-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 - @backstage/plugin-lighthouse-backend@0.3.2-next.1 - @backstage/plugin-linguist-backend@0.5.2-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.9-next.1 - @backstage/plugin-search-backend-module-explore@0.1.9-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.9-next.1 - @backstage/plugin-search-backend-node@1.2.9-next.1 - - @backstage/plugin-tech-insights-backend@0.5.19-next.1 - - @backstage/plugin-tech-insights-node@0.4.11-next.1 - - example-app@0.2.88-next.1 - - @backstage/plugin-auth-backend@0.19.2-next.1 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.2-next.1 - @backstage/plugin-kubernetes-backend@0.12.2-next.1 - @backstage/plugin-todo-backend@0.3.3-next.1 + - @backstage/backend-defaults@0.2.5-next.1 - @backstage/plugin-adr-backend@0.4.2-next.1 - @backstage/plugin-app-backend@0.3.53-next.1 - @backstage/plugin-auth-node@0.3.2-next.1 - @backstage/plugin-azure-devops-backend@0.4.2-next.1 - - @backstage/plugin-azure-sites-backend@0.1.15-next.1 - - @backstage/plugin-code-coverage-backend@0.2.19-next.1 - @backstage/plugin-devtools-backend@0.2.2-next.1 - @backstage/plugin-entity-feedback-backend@0.2.2-next.1 - - @backstage/plugin-events-backend@0.2.14-next.1 - - @backstage/plugin-explore-backend@0.0.15-next.1 - - @backstage/plugin-graphql-backend@0.1.43-next.1 - - @backstage/plugin-jenkins-backend@0.2.8-next.1 - - @backstage/plugin-kafka-backend@0.3.2-next.1 - - @backstage/plugin-nomad-backend@0.1.7-next.1 - @backstage/plugin-permission-backend@0.5.28-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.2-next.1 - @backstage/plugin-permission-node@0.7.16-next.1 - @backstage/plugin-playlist-backend@0.3.9-next.1 - @backstage/plugin-proxy-backend@0.4.2-next.1 - - @backstage/plugin-rollbar-backend@0.1.50-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.6-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.22-next.1 - @backstage/plugin-search-backend@1.4.5-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.8-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.14-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.37-next.1 + - @backstage/plugin-sonarqube-backend@0.2.7-next.1 - @backstage/plugin-techdocs-backend@1.7.2-next.1 - - @backstage/config@1.1.0 - - @backstage/catalog-client@1.4.4 - - @backstage/catalog-model@1.4.2 - - @backstage/integration@1.7.1-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.2-next.1 - - @backstage/plugin-events-node@0.2.14-next.1 - @backstage/plugin-permission-common@0.7.8 - - @backstage/plugin-search-common@1.2.6 -## 0.2.88-next.0 +## 0.0.16-next.0 ### Patch Changes - Updated dependencies - - @backstage/plugin-search-backend-module-elasticsearch@1.3.8-next.0 - - @backstage/integration@1.7.1-next.0 + - @backstage/plugin-sonarqube-backend@0.2.7-next.0 - @backstage/plugin-playlist-backend@0.3.9-next.0 - - @backstage/plugin-rollbar-backend@0.1.50-next.0 - @backstage/plugin-catalog-backend@1.14.0-next.0 - - @backstage/plugin-tech-insights-backend@0.5.19-next.0 - - @backstage/plugin-code-coverage-backend@0.2.19-next.0 - @backstage/plugin-auth-node@0.3.2-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.22-next.0 - - @backstage/backend-common@0.19.7-next.0 - - example-app@0.2.88-next.0 - @backstage/plugin-adr-backend@0.4.2-next.0 - @backstage/plugin-scaffolder-backend@1.17.3-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.6-next.0 - @backstage/plugin-techdocs-backend@1.7.2-next.0 - @backstage/plugin-todo-backend@0.3.3-next.0 - - @backstage/config@1.1.0 + - @backstage/backend-defaults@0.2.5-next.0 + - @backstage/backend-plugin-api@0.6.5-next.0 - @backstage/backend-tasks@0.5.10-next.0 - - @backstage/catalog-client@1.4.4 - - @backstage/catalog-model@1.4.2 - @backstage/plugin-app-backend@0.3.53-next.0 - - @backstage/plugin-auth-backend@0.19.2-next.0 - @backstage/plugin-azure-devops-backend@0.4.2-next.0 - - @backstage/plugin-azure-sites-backend@0.1.15-next.0 - @backstage/plugin-badges-backend@0.3.2-next.0 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.2-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.2-next.0 - - @backstage/plugin-catalog-node@1.4.6-next.0 - @backstage/plugin-devtools-backend@0.2.2-next.0 - @backstage/plugin-entity-feedback-backend@0.2.2-next.0 - - @backstage/plugin-events-backend@0.2.14-next.0 - - @backstage/plugin-events-node@0.2.14-next.0 - - @backstage/plugin-explore-backend@0.0.15-next.0 - - @backstage/plugin-graphql-backend@0.1.43-next.0 - - @backstage/plugin-jenkins-backend@0.2.8-next.0 - - @backstage/plugin-kafka-backend@0.3.2-next.0 - @backstage/plugin-kubernetes-backend@0.12.2-next.0 - @backstage/plugin-lighthouse-backend@0.3.2-next.0 - @backstage/plugin-linguist-backend@0.5.2-next.0 - - @backstage/plugin-nomad-backend@0.1.7-next.0 - @backstage/plugin-permission-backend@0.5.28-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.2-next.0 - @backstage/plugin-permission-common@0.7.8 - @backstage/plugin-permission-node@0.7.16-next.0 - @backstage/plugin-proxy-backend@0.4.2-next.0 - @backstage/plugin-search-backend@1.4.5-next.0 - @backstage/plugin-search-backend-module-catalog@0.1.9-next.0 - @backstage/plugin-search-backend-module-explore@0.1.9-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.14-next.0 - @backstage/plugin-search-backend-module-techdocs@0.1.9-next.0 - @backstage/plugin-search-backend-node@1.2.9-next.0 - - @backstage/plugin-search-common@1.2.6 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.37-next.0 - - @backstage/plugin-tech-insights-node@0.4.11-next.0 -## 0.2.87 +## 0.0.15 ### Patch Changes - Updated dependencies - - @backstage/plugin-search-backend-module-pg@0.5.12 - @backstage/plugin-catalog-backend@1.13.0 - @backstage/plugin-kubernetes-backend@0.12.0 - @backstage/plugin-techdocs-backend@1.7.0 - - @backstage/plugin-auth-backend@0.19.0 - @backstage/plugin-proxy-backend@0.4.0 - @backstage/plugin-adr-backend@0.4.0 - @backstage/plugin-azure-devops-backend@0.4.0 @@ -1942,49 +1437,27 @@ - @backstage/plugin-catalog-backend-module-unprocessed@0.3.0 - @backstage/plugin-devtools-backend@0.2.0 - @backstage/plugin-entity-feedback-backend@0.2.0 - - @backstage/plugin-kafka-backend@0.3.0 - @backstage/plugin-lighthouse-backend@0.3.0 - @backstage/plugin-linguist-backend@0.5.0 - @backstage/plugin-todo-backend@0.3.0 - @backstage/plugin-app-backend@0.3.51 - - @backstage/plugin-events-backend@0.2.12 - @backstage/plugin-permission-backend@0.5.26 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.0 - @backstage/plugin-scaffolder-backend@1.17.0 - @backstage/plugin-search-backend@1.4.3 - @backstage/plugin-search-backend-module-catalog@0.1.7 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.6 - @backstage/plugin-search-backend-module-explore@0.1.7 - @backstage/plugin-search-backend-module-techdocs@0.1.7 - - @backstage/plugin-code-coverage-backend@0.2.17 - @backstage/backend-tasks@0.5.8 - - @backstage/backend-common@0.19.5 - @backstage/plugin-auth-node@0.3.0 - - @backstage/config@1.1.0 - - @backstage/catalog-client@1.4.4 - - @backstage/catalog-model@1.4.2 - - @backstage/integration@1.7.0 - @backstage/plugin-permission-common@0.7.8 - - @backstage/plugin-search-common@1.2.6 - @backstage/plugin-permission-node@0.7.14 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35 + - @backstage/backend-plugin-api@0.6.3 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.0 - - @backstage/plugin-tech-insights-backend@0.5.17 - - example-app@0.2.87 - - @backstage/plugin-catalog-node@1.4.4 - - @backstage/plugin-playlist-backend@0.3.7 - - @backstage/plugin-rollbar-backend@0.1.48 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4 - - @backstage/plugin-azure-sites-backend@0.1.13 - - @backstage/plugin-events-node@0.2.12 - - @backstage/plugin-explore-backend@0.0.13 - - @backstage/plugin-graphql-backend@0.1.41 - - @backstage/plugin-jenkins-backend@0.2.6 - - @backstage/plugin-nomad-backend@0.1.5 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.20 + - @backstage/backend-defaults@0.2.3 - @backstage/plugin-search-backend-node@1.2.7 - - @backstage/plugin-tech-insights-node@0.4.9 -## 0.2.87-next.3 +## 0.0.15-next.3 ### Patch Changes @@ -1992,388 +1465,230 @@ - @backstage/plugin-techdocs-backend@1.7.0-next.3 - @backstage/plugin-proxy-backend@0.4.0-next.3 - @backstage/plugin-adr-backend@0.4.0-next.3 - - @backstage/plugin-auth-backend@0.19.0-next.3 - @backstage/plugin-azure-devops-backend@0.4.0-next.3 - @backstage/plugin-badges-backend@0.3.0-next.3 - @backstage/plugin-catalog-backend-module-unprocessed@0.3.0-next.3 - @backstage/plugin-devtools-backend@0.2.0-next.3 - @backstage/plugin-entity-feedback-backend@0.2.0-next.3 - - @backstage/plugin-kafka-backend@0.3.0-next.3 - @backstage/plugin-lighthouse-backend@0.3.0-next.3 - @backstage/plugin-linguist-backend@0.5.0-next.3 - @backstage/plugin-todo-backend@0.3.0-next.3 - @backstage/plugin-app-backend@0.3.51-next.3 - @backstage/plugin-catalog-backend@1.13.0-next.3 - - @backstage/plugin-events-backend@0.2.12-next.3 - @backstage/plugin-kubernetes-backend@0.11.6-next.3 - @backstage/plugin-permission-backend@0.5.26-next.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.0-next.1 - @backstage/plugin-scaffolder-backend@1.17.0-next.3 - @backstage/plugin-search-backend@1.4.3-next.3 - @backstage/plugin-search-backend-module-catalog@0.1.7-next.3 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.6-next.3 - @backstage/plugin-search-backend-module-explore@0.1.7-next.3 - - @backstage/plugin-search-backend-module-pg@0.5.12-next.3 - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.3 - - @backstage/catalog-client@1.4.4-next.2 - - @backstage/catalog-model@1.4.2-next.2 - - @backstage/config@1.1.0-next.2 - - @backstage/integration@1.7.0-next.3 - @backstage/plugin-permission-common@0.7.8-next.2 - - @backstage/plugin-search-common@1.2.6-next.2 - @backstage/plugin-permission-node@0.7.14-next.3 + - @backstage/backend-plugin-api@0.6.3-next.3 - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.0-next.0 - - example-app@0.2.87-next.3 - - @backstage/backend-common@0.19.5-next.3 - - @backstage/plugin-explore-backend@0.0.13-next.3 + - @backstage/backend-defaults@0.2.3-next.3 - @backstage/backend-tasks@0.5.8-next.3 - @backstage/plugin-auth-node@0.3.0-next.3 - - @backstage/plugin-azure-sites-backend@0.1.13-next.3 - - @backstage/plugin-catalog-node@1.4.4-next.3 - - @backstage/plugin-code-coverage-backend@0.2.17-next.3 - - @backstage/plugin-events-node@0.2.12-next.3 - - @backstage/plugin-graphql-backend@0.1.41-next.3 - - @backstage/plugin-jenkins-backend@0.2.6-next.3 - - @backstage/plugin-nomad-backend@0.1.5-next.3 - - @backstage/plugin-playlist-backend@0.3.7-next.3 - - @backstage/plugin-rollbar-backend@0.1.48-next.3 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4-next.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.20-next.3 - @backstage/plugin-search-backend-node@1.2.7-next.3 - - @backstage/plugin-tech-insights-backend@0.5.17-next.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35-next.3 - - @backstage/plugin-tech-insights-node@0.4.9-next.3 -## 0.2.87-next.2 +## 0.0.15-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.16.6-next.2 - - @backstage/plugin-code-coverage-backend@0.2.17-next.2 - @backstage/plugin-permission-backend@0.5.26-next.2 - @backstage/plugin-catalog-backend@1.13.0-next.2 - @backstage/plugin-badges-backend@0.2.6-next.2 - - @backstage/config@1.1.0-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35-next.2 - - @backstage/plugin-tech-insights-backend@0.5.17-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.0-next.0 - @backstage/backend-tasks@0.5.8-next.2 - - example-app@0.2.87-next.2 - - @backstage/backend-common@0.19.5-next.2 + - @backstage/backend-defaults@0.2.3-next.2 - @backstage/plugin-app-backend@0.3.51-next.2 - - @backstage/plugin-auth-backend@0.18.9-next.2 - @backstage/plugin-auth-node@0.3.0-next.2 - - @backstage/plugin-catalog-node@1.4.4-next.2 - @backstage/plugin-entity-feedback-backend@0.1.9-next.2 - - @backstage/plugin-events-backend@0.2.12-next.2 - @backstage/plugin-kubernetes-backend@0.11.6-next.2 - @backstage/plugin-linguist-backend@0.4.3-next.2 - @backstage/plugin-permission-node@0.7.14-next.2 - - @backstage/plugin-playlist-backend@0.3.7-next.2 - @backstage/plugin-proxy-backend@0.3.3-next.2 - - @backstage/plugin-rollbar-backend@0.1.48-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4-next.2 - @backstage/plugin-search-backend@1.4.3-next.2 - @backstage/plugin-search-backend-module-catalog@0.1.7-next.2 - @backstage/plugin-search-backend-module-explore@0.1.7-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.12-next.2 - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.2 - @backstage/plugin-techdocs-backend@1.7.0-next.2 - - @backstage/integration@1.7.0-next.2 - @backstage/plugin-devtools-backend@0.1.6-next.2 - - @backstage/catalog-model@1.4.2-next.1 + - @backstage/backend-plugin-api@0.6.3-next.2 - @backstage/plugin-adr-backend@0.3.9-next.2 - @backstage/plugin-azure-devops-backend@0.3.30-next.2 - - @backstage/plugin-azure-sites-backend@0.1.13-next.2 - - @backstage/plugin-explore-backend@0.0.13-next.2 - - @backstage/plugin-graphql-backend@0.1.41-next.2 - - @backstage/plugin-jenkins-backend@0.2.6-next.2 - - @backstage/plugin-kafka-backend@0.2.44-next.2 - @backstage/plugin-lighthouse-backend@0.2.7-next.2 - - @backstage/plugin-nomad-backend@0.1.5-next.2 - @backstage/plugin-permission-common@0.7.8-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.20-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.6-next.2 - @backstage/plugin-search-backend-node@1.2.7-next.2 - - @backstage/plugin-tech-insights-node@0.4.9-next.2 - @backstage/plugin-todo-backend@0.2.3-next.2 - - @backstage/catalog-client@1.4.4-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.2.3-next.2 - - @backstage/plugin-events-node@0.2.12-next.2 - - @backstage/plugin-search-common@1.2.6-next.1 -## 0.2.87-next.1 +## 0.0.15-next.1 ### Patch Changes - Updated dependencies - - @backstage/plugin-search-backend-module-pg@0.5.12-next.1 - @backstage/plugin-kubernetes-backend@0.11.6-next.1 - @backstage/plugin-catalog-backend@1.13.0-next.1 - - @backstage/plugin-auth-backend@0.18.9-next.1 - - @backstage/config@1.1.0-next.0 - - @backstage/integration@1.7.0-next.1 - @backstage/plugin-devtools-backend@0.1.6-next.1 - @backstage/backend-tasks@0.5.8-next.1 - @backstage/plugin-techdocs-backend@1.7.0-next.1 - @backstage/plugin-scaffolder-backend@1.16.6-next.1 - - @backstage/plugin-code-coverage-backend@0.2.17-next.1 - - example-app@0.2.87-next.1 - - @backstage/backend-common@0.19.5-next.1 - - @backstage/catalog-model@1.4.2-next.0 + - @backstage/backend-plugin-api@0.6.3-next.1 - @backstage/plugin-adr-backend@0.3.9-next.1 - @backstage/plugin-app-backend@0.3.51-next.1 - @backstage/plugin-auth-node@0.3.0-next.1 - @backstage/plugin-azure-devops-backend@0.3.30-next.1 - - @backstage/plugin-azure-sites-backend@0.1.13-next.1 - @backstage/plugin-badges-backend@0.2.6-next.1 - @backstage/plugin-entity-feedback-backend@0.1.9-next.1 - - @backstage/plugin-events-backend@0.2.12-next.1 - - @backstage/plugin-explore-backend@0.0.13-next.1 - - @backstage/plugin-graphql-backend@0.1.41-next.1 - - @backstage/plugin-jenkins-backend@0.2.6-next.1 - - @backstage/plugin-kafka-backend@0.2.44-next.1 - @backstage/plugin-lighthouse-backend@0.2.7-next.1 - @backstage/plugin-linguist-backend@0.4.3-next.1 - - @backstage/plugin-nomad-backend@0.1.5-next.1 - @backstage/plugin-permission-backend@0.5.26-next.1 - @backstage/plugin-permission-common@0.7.8-next.0 - @backstage/plugin-permission-node@0.7.14-next.1 - - @backstage/plugin-playlist-backend@0.3.7-next.1 - @backstage/plugin-proxy-backend@0.3.3-next.1 - - @backstage/plugin-rollbar-backend@0.1.48-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.4-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.20-next.1 - @backstage/plugin-search-backend@1.4.3-next.1 - @backstage/plugin-search-backend-module-catalog@0.1.7-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.6-next.1 - @backstage/plugin-search-backend-module-explore@0.1.7-next.1 - @backstage/plugin-search-backend-module-techdocs@0.1.7-next.1 - @backstage/plugin-search-backend-node@1.2.7-next.1 - - @backstage/plugin-tech-insights-backend@0.5.17-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.35-next.1 - - @backstage/plugin-tech-insights-node@0.4.9-next.1 - @backstage/plugin-todo-backend@0.2.3-next.1 - - @backstage/plugin-catalog-node@1.4.4-next.1 + - @backstage/backend-defaults@0.2.3-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.2.3-next.1 - - @backstage/plugin-events-node@0.2.12-next.1 - - @backstage/catalog-client@1.4.4-next.0 - - @backstage/plugin-search-common@1.2.6-next.0 -## 0.2.87-next.0 +## 0.0.15-next.0 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.12.2-next.0 - - @backstage/plugin-auth-backend@0.18.8-next.0 - - @backstage/plugin-code-coverage-backend@0.2.16-next.0 - @backstage/plugin-scaffolder-backend@1.16.3-next.0 - @backstage/plugin-auth-node@0.3.0-next.0 - - @backstage/backend-common@0.19.4-next.0 - @backstage/plugin-linguist-backend@0.4.2-next.0 - - @backstage/integration@1.7.0-next.0 - @backstage/plugin-entity-feedback-backend@0.1.8-next.0 - - @backstage/plugin-tech-insights-backend@0.5.16-next.0 - @backstage/backend-tasks@0.5.7-next.0 - @backstage/plugin-app-backend@0.3.50-next.0 - - example-app@0.2.87-next.0 - - @backstage/catalog-client@1.4.3 - - @backstage/catalog-model@1.4.1 - - @backstage/config@1.0.8 + - @backstage/backend-defaults@0.2.2-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 - @backstage/plugin-adr-backend@0.3.8-next.0 - @backstage/plugin-azure-devops-backend@0.3.29-next.0 - - @backstage/plugin-azure-sites-backend@0.1.12-next.0 - @backstage/plugin-badges-backend@0.2.5-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.2.2-next.0 - - @backstage/plugin-catalog-node@1.4.3-next.0 - @backstage/plugin-devtools-backend@0.1.5-next.0 - - @backstage/plugin-events-backend@0.2.11-next.0 - - @backstage/plugin-events-node@0.2.11-next.0 - - @backstage/plugin-explore-backend@0.0.12-next.0 - - @backstage/plugin-graphql-backend@0.1.40-next.0 - - @backstage/plugin-jenkins-backend@0.2.5-next.0 - - @backstage/plugin-kafka-backend@0.2.43-next.0 - @backstage/plugin-kubernetes-backend@0.11.5-next.0 - @backstage/plugin-lighthouse-backend@0.2.6-next.0 - - @backstage/plugin-nomad-backend@0.1.4-next.0 - @backstage/plugin-permission-backend@0.5.25-next.0 - @backstage/plugin-permission-common@0.7.7 - @backstage/plugin-permission-node@0.7.13-next.0 - - @backstage/plugin-playlist-backend@0.3.6-next.0 - @backstage/plugin-proxy-backend@0.3.2-next.0 - - @backstage/plugin-rollbar-backend@0.1.47-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.3-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.19-next.0 - @backstage/plugin-search-backend@1.4.2-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.5-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.11-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.6-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.6-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.6-next.0 - @backstage/plugin-search-backend-node@1.2.6-next.0 - - @backstage/plugin-search-common@1.2.5 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.34-next.0 - - @backstage/plugin-tech-insights-node@0.4.8-next.0 - @backstage/plugin-techdocs-backend@1.6.7-next.0 - @backstage/plugin-todo-backend@0.2.2-next.0 -## 0.2.86 +## 0.0.14 ### Patch Changes - Updated dependencies - - @backstage/plugin-search-backend-module-elasticsearch@1.3.3 - - @backstage/plugin-search-backend-module-pg@0.5.9 + - @backstage/plugin-search-backend-module-techdocs@0.1.4 + - @backstage/plugin-search-backend-module-catalog@0.1.4 + - @backstage/plugin-search-backend-module-explore@0.1.4 - @backstage/plugin-azure-devops-backend@0.3.27 - @backstage/plugin-kubernetes-backend@0.11.3 - @backstage/plugin-lighthouse-backend@0.2.4 - @backstage/plugin-permission-backend@0.5.23 - @backstage/plugin-scaffolder-backend@1.16.0 + - @backstage/backend-defaults@0.2.0 - @backstage/plugin-devtools-backend@0.1.3 - @backstage/plugin-techdocs-backend@1.6.5 - - @backstage/backend-common@0.19.2 - @backstage/plugin-catalog-backend@1.12.0 - @backstage/plugin-badges-backend@0.2.3 - - @backstage/plugin-events-backend@0.2.9 - @backstage/plugin-search-backend@1.4.0 - - @backstage/plugin-kafka-backend@0.2.41 - @backstage/plugin-proxy-backend@0.3.0 - @backstage/plugin-todo-backend@0.2.0 - @backstage/plugin-app-backend@0.3.48 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1 - - @backstage/plugin-auth-backend@0.18.6 - - @backstage/plugin-explore-backend@0.0.10 + - @backstage/backend-plugin-api@0.6.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.17 - @backstage/plugin-entity-feedback-backend@0.1.6 - - @backstage/plugin-code-coverage-backend@0.2.14 - @backstage/plugin-search-backend-node@1.2.4 - @backstage/plugin-linguist-backend@0.4.0 - - @backstage/plugin-playlist-backend@0.3.4 - - @backstage/plugin-jenkins-backend@0.2.3 - - @backstage/plugin-nomad-backend@0.1.2 - - @backstage/plugin-catalog-node@1.4.1 - - @backstage/plugin-events-node@0.2.9 - @backstage/plugin-auth-node@0.2.17 - - @backstage/integration@1.6.0 - @backstage/backend-tasks@0.5.5 - - example-app@0.2.86 - @backstage/plugin-adr-backend@0.3.6 - - @backstage/plugin-azure-sites-backend@0.1.10 - - @backstage/plugin-graphql-backend@0.1.38 - @backstage/plugin-permission-node@0.7.11 - - @backstage/plugin-rollbar-backend@0.1.45 - - @backstage/plugin-tech-insights-backend@0.5.14 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32 - - @backstage/plugin-tech-insights-node@0.4.6 - - @backstage/catalog-client@1.4.3 - - @backstage/catalog-model@1.4.1 - - @backstage/config@1.0.8 - @backstage/plugin-permission-common@0.7.7 - - @backstage/plugin-search-common@1.2.5 -## 0.2.86-next.2 +## 0.0.14-next.2 ### Patch Changes - Updated dependencies - - @backstage/plugin-auth-backend@0.18.6-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.4-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.4-next.2 - @backstage/plugin-scaffolder-backend@1.15.2-next.2 - - @backstage/plugin-explore-backend@0.0.10-next.2 - @backstage/plugin-catalog-backend@1.12.0-next.2 + - @backstage/backend-plugin-api@0.6.0-next.2 - @backstage/plugin-proxy-backend@0.3.0-next.2 - @backstage/backend-tasks@0.5.5-next.2 - @backstage/plugin-app-backend@0.3.48-next.2 - @backstage/plugin-linguist-backend@0.4.0-next.2 - @backstage/plugin-techdocs-backend@1.6.5-next.2 - - @backstage/backend-common@0.19.2-next.2 - - example-app@0.2.86-next.2 + - @backstage/backend-defaults@0.2.0-next.2 - @backstage/plugin-adr-backend@0.3.6-next.2 - @backstage/plugin-azure-devops-backend@0.3.27-next.2 - @backstage/plugin-badges-backend@0.2.3-next.2 - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.2 - - @backstage/plugin-catalog-node@1.4.1-next.2 - @backstage/plugin-devtools-backend@0.1.3-next.2 - @backstage/plugin-entity-feedback-backend@0.1.6-next.2 - - @backstage/plugin-events-backend@0.2.9-next.2 - - @backstage/plugin-events-node@0.2.9-next.2 - - @backstage/plugin-kafka-backend@0.2.41-next.2 - @backstage/plugin-kubernetes-backend@0.11.3-next.2 - @backstage/plugin-lighthouse-backend@0.2.4-next.2 - @backstage/plugin-permission-backend@0.5.23-next.2 - @backstage/plugin-permission-node@0.7.11-next.2 - @backstage/plugin-search-backend@1.4.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.9-next.2 - @backstage/plugin-search-backend-node@1.2.4-next.2 - @backstage/plugin-todo-backend@0.2.0-next.2 - - @backstage/plugin-tech-insights-backend@0.5.14-next.2 - - @backstage/plugin-tech-insights-node@0.4.6-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.2 - @backstage/plugin-auth-node@0.2.17-next.2 - - @backstage/plugin-azure-sites-backend@0.1.10-next.2 - - @backstage/plugin-code-coverage-backend@0.2.14-next.2 - - @backstage/plugin-graphql-backend@0.1.38-next.2 - - @backstage/plugin-jenkins-backend@0.2.3-next.2 - - @backstage/plugin-nomad-backend@0.1.2-next.2 - - @backstage/plugin-playlist-backend@0.3.4-next.2 - - @backstage/plugin-rollbar-backend@0.1.45-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.2 -## 0.2.86-next.1 +## 0.0.14-next.1 ### Patch Changes - Updated dependencies - - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.9-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.4-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.4-next.1 - @backstage/plugin-azure-devops-backend@0.3.27-next.1 - @backstage/plugin-kubernetes-backend@0.11.3-next.1 - @backstage/plugin-lighthouse-backend@0.2.4-next.1 - @backstage/plugin-permission-backend@0.5.23-next.1 - @backstage/plugin-scaffolder-backend@1.15.2-next.1 + - @backstage/backend-defaults@0.2.0-next.1 - @backstage/plugin-devtools-backend@0.1.3-next.1 - @backstage/plugin-techdocs-backend@1.6.5-next.1 - - @backstage/backend-common@0.19.2-next.1 - @backstage/plugin-catalog-backend@1.12.0-next.1 - @backstage/plugin-badges-backend@0.2.3-next.1 - - @backstage/plugin-events-backend@0.2.9-next.1 - @backstage/plugin-search-backend@1.4.0-next.1 - - @backstage/plugin-kafka-backend@0.2.41-next.1 - - @backstage/plugin-proxy-backend@0.2.42-next.1 - @backstage/plugin-todo-backend@0.2.0-next.1 - @backstage/plugin-app-backend@0.3.48-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.1 - @backstage/plugin-entity-feedback-backend@0.1.6-next.1 - - @backstage/plugin-code-coverage-backend@0.2.14-next.1 - @backstage/plugin-search-backend-node@1.2.4-next.1 - @backstage/plugin-linguist-backend@0.3.2-next.1 - - @backstage/plugin-playlist-backend@0.3.4-next.1 - - @backstage/plugin-explore-backend@0.0.10-next.1 - - @backstage/plugin-jenkins-backend@0.2.3-next.1 - - @backstage/plugin-nomad-backend@0.1.2-next.1 - - @backstage/plugin-catalog-node@1.4.1-next.1 - - @backstage/plugin-events-node@0.2.9-next.1 - @backstage/plugin-auth-node@0.2.17-next.1 - - @backstage/plugin-auth-backend@0.18.6-next.1 - @backstage/backend-tasks@0.5.5-next.1 - @backstage/plugin-adr-backend@0.3.6-next.1 - - @backstage/plugin-azure-sites-backend@0.1.10-next.1 - - @backstage/plugin-graphql-backend@0.1.38-next.1 - @backstage/plugin-permission-node@0.7.11-next.1 - - @backstage/plugin-rollbar-backend@0.1.45-next.1 - - @backstage/plugin-tech-insights-backend@0.5.14-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.1 - - @backstage/plugin-tech-insights-node@0.4.6-next.1 - - example-app@0.2.86-next.1 - - @backstage/integration@1.5.1 - - @backstage/catalog-client@1.4.3 - - @backstage/catalog-model@1.4.1 - - @backstage/config@1.0.8 - @backstage/plugin-permission-common@0.7.7 - - @backstage/plugin-search-common@1.2.5 -## 0.2.86-next.0 +## 0.0.14-next.0 ### Patch Changes @@ -2384,61 +1699,34 @@ - @backstage/plugin-todo-backend@0.2.0-next.0 - @backstage/plugin-catalog-backend@1.12.0-next.0 - @backstage/plugin-search-backend@1.4.0-next.0 - - example-app@0.2.86-next.0 - - @backstage/backend-common@0.19.2-next.0 + - @backstage/backend-defaults@0.1.13-next.0 - @backstage/backend-tasks@0.5.5-next.0 - - @backstage/catalog-client@1.4.3 - - @backstage/catalog-model@1.4.1 - - @backstage/config@1.0.8 - - @backstage/integration@1.5.1 - @backstage/plugin-adr-backend@0.3.6-next.0 - @backstage/plugin-app-backend@0.3.48-next.0 - - @backstage/plugin-auth-backend@0.18.6-next.0 - @backstage/plugin-auth-node@0.2.17-next.0 - @backstage/plugin-azure-devops-backend@0.3.27-next.0 - - @backstage/plugin-azure-sites-backend@0.1.10-next.0 - @backstage/plugin-badges-backend@0.2.3-next.0 - - @backstage/plugin-catalog-node@1.4.1-next.0 - - @backstage/plugin-code-coverage-backend@0.2.14-next.0 - @backstage/plugin-devtools-backend@0.1.3-next.0 - @backstage/plugin-entity-feedback-backend@0.1.6-next.0 - - @backstage/plugin-events-backend@0.2.9-next.0 - - @backstage/plugin-events-node@0.2.9-next.0 - - @backstage/plugin-explore-backend@0.0.10-next.0 - - @backstage/plugin-graphql-backend@0.1.38-next.0 - - @backstage/plugin-jenkins-backend@0.2.3-next.0 - - @backstage/plugin-kafka-backend@0.2.41-next.0 - @backstage/plugin-kubernetes-backend@0.11.3-next.0 - @backstage/plugin-lighthouse-backend@0.2.4-next.0 - - @backstage/plugin-nomad-backend@0.1.2-next.0 - @backstage/plugin-permission-backend@0.5.23-next.0 - @backstage/plugin-permission-common@0.7.7 - @backstage/plugin-permission-node@0.7.11-next.0 - - @backstage/plugin-playlist-backend@0.3.4-next.0 - - @backstage/plugin-proxy-backend@0.2.42-next.0 - - @backstage/plugin-rollbar-backend@0.1.45-next.0 - @backstage/plugin-scaffolder-backend@1.15.2-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.9-next.0 - - @backstage/plugin-search-common@1.2.5 - - @backstage/plugin-tech-insights-backend@0.5.14-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.0 - - @backstage/plugin-tech-insights-node@0.4.6-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.4-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.4-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.0 - @backstage/plugin-techdocs-backend@1.6.5-next.0 -## 0.2.85 +## 0.0.13 ### Patch Changes - Updated dependencies - @backstage/plugin-kubernetes-backend@0.11.2 - @backstage/plugin-badges-backend@0.2.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.0 - @backstage/plugin-devtools-backend@0.1.2 - - @backstage/plugin-tech-insights-backend@0.5.13 - - @backstage/backend-common@0.19.1 - @backstage/plugin-scaffolder-backend@1.15.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1 - @backstage/plugin-azure-devops-backend@0.3.26 @@ -2447,98 +1735,52 @@ - @backstage/plugin-lighthouse-backend@0.2.3 - @backstage/plugin-entity-feedback-backend@0.1.5 - @backstage/plugin-catalog-backend@1.11.0 - - @backstage/plugin-catalog-node@1.4.0 - - @backstage/plugin-auth-backend@0.18.5 - - example-app@0.2.85 + - @backstage/backend-defaults@0.1.12 - @backstage/backend-tasks@0.5.4 - - @backstage/catalog-client@1.4.3 - - @backstage/catalog-model@1.4.1 - - @backstage/config@1.0.8 - - @backstage/integration@1.5.1 - @backstage/plugin-app-backend@0.3.47 - @backstage/plugin-auth-node@0.2.16 - - @backstage/plugin-azure-sites-backend@0.1.9 - - @backstage/plugin-code-coverage-backend@0.2.13 - - @backstage/plugin-events-backend@0.2.8 - - @backstage/plugin-events-node@0.2.8 - - @backstage/plugin-explore-backend@0.0.9 - - @backstage/plugin-graphql-backend@0.1.37 - - @backstage/plugin-jenkins-backend@0.2.2 - - @backstage/plugin-kafka-backend@0.2.40 - - @backstage/plugin-nomad-backend@0.1.1 - @backstage/plugin-permission-backend@0.5.22 - @backstage/plugin-permission-common@0.7.7 - @backstage/plugin-permission-node@0.7.10 - - @backstage/plugin-playlist-backend@0.3.3 - - @backstage/plugin-proxy-backend@0.2.41 - - @backstage/plugin-rollbar-backend@0.1.44 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.16 - @backstage/plugin-search-backend@1.3.3 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.2 - - @backstage/plugin-search-backend-module-pg@0.5.8 + - @backstage/plugin-search-backend-module-catalog@0.1.3 + - @backstage/plugin-search-backend-module-explore@0.1.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.3 - @backstage/plugin-search-backend-node@1.2.3 - - @backstage/plugin-search-common@1.2.5 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.31 - - @backstage/plugin-tech-insights-node@0.4.5 - @backstage/plugin-techdocs-backend@1.6.4 - @backstage/plugin-todo-backend@0.1.44 -## 0.2.85-next.2 +## 0.0.13-next.2 ### Patch Changes - Updated dependencies - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.0-next.1 - @backstage/plugin-devtools-backend@0.1.2-next.2 - - @backstage/plugin-tech-insights-backend@0.5.13-next.1 - @backstage/plugin-scaffolder-backend@1.15.1-next.1 - @backstage/plugin-kubernetes-backend@0.11.2-next.2 - @backstage/plugin-adr-backend@0.3.5-next.1 - - example-app@0.2.85-next.2 - - @backstage/backend-common@0.19.1-next.0 + - @backstage/backend-defaults@0.1.12-next.0 - @backstage/backend-tasks@0.5.4-next.0 - - @backstage/catalog-client@1.4.3-next.0 - - @backstage/catalog-model@1.4.1-next.0 - - @backstage/config@1.0.8 - - @backstage/integration@1.5.1-next.0 - @backstage/plugin-app-backend@0.3.47-next.0 - - @backstage/plugin-auth-backend@0.18.5-next.1 - @backstage/plugin-auth-node@0.2.16-next.0 - @backstage/plugin-azure-devops-backend@0.3.26-next.1 - - @backstage/plugin-azure-sites-backend@0.1.9-next.0 - @backstage/plugin-badges-backend@0.2.2-next.1 - @backstage/plugin-catalog-backend@1.11.0-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1-next.0 - - @backstage/plugin-catalog-node@1.4.0-next.0 - - @backstage/plugin-code-coverage-backend@0.2.13-next.0 - @backstage/plugin-entity-feedback-backend@0.1.5-next.0 - - @backstage/plugin-events-backend@0.2.8-next.0 - - @backstage/plugin-events-node@0.2.8-next.0 - - @backstage/plugin-explore-backend@0.0.9-next.0 - - @backstage/plugin-graphql-backend@0.1.37-next.0 - - @backstage/plugin-jenkins-backend@0.2.2-next.0 - - @backstage/plugin-kafka-backend@0.2.40-next.0 - - @backstage/plugin-lighthouse-backend@0.2.3-next.0 - @backstage/plugin-linguist-backend@0.3.1-next.1 - - @backstage/plugin-nomad-backend@0.1.1-next.0 - @backstage/plugin-permission-backend@0.5.22-next.0 - @backstage/plugin-permission-common@0.7.7-next.0 - @backstage/plugin-permission-node@0.7.10-next.0 - - @backstage/plugin-playlist-backend@0.3.3-next.0 - - @backstage/plugin-proxy-backend@0.2.41-next.0 - - @backstage/plugin-rollbar-backend@0.1.44-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.16-next.1 - @backstage/plugin-search-backend@1.3.3-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.2-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.8-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.3-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.3-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.3-next.0 - @backstage/plugin-search-backend-node@1.2.3-next.0 - - @backstage/plugin-search-common@1.2.5-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.31-next.0 - - @backstage/plugin-tech-insights-node@0.4.5-next.0 - @backstage/plugin-techdocs-backend@1.6.4-next.0 - @backstage/plugin-todo-backend@0.1.44-next.0 -## 0.2.85-next.1 +## 0.0.13-next.1 ### Patch Changes @@ -2548,4340 +1790,611 @@ - @backstage/plugin-azure-devops-backend@0.3.26-next.1 - @backstage/plugin-devtools-backend@0.1.2-next.1 - @backstage/plugin-linguist-backend@0.3.1-next.1 - - @backstage/plugin-auth-backend@0.18.5-next.1 - - example-app@0.2.85-next.1 - - @backstage/config@1.0.8 -## 0.2.85-next.0 +## 0.0.13-next.0 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.19.1-next.0 - @backstage/plugin-catalog-backend-module-unprocessed@0.1.1-next.0 - @backstage/plugin-entity-feedback-backend@0.1.5-next.0 - @backstage/plugin-catalog-backend@1.11.0-next.0 - - @backstage/plugin-catalog-node@1.4.0-next.0 - @backstage/plugin-kubernetes-backend@0.11.2-next.0 - - example-app@0.2.85-next.0 - - @backstage/backend-tasks@0.5.4-next.0 - - @backstage/catalog-client@1.4.3-next.0 - - @backstage/catalog-model@1.4.1-next.0 - - @backstage/config@1.0.8 - - @backstage/integration@1.5.1-next.0 - - @backstage/plugin-adr-backend@0.3.5-next.0 + - @backstage/backend-defaults@0.1.12-next.0 - @backstage/plugin-app-backend@0.3.47-next.0 - - @backstage/plugin-auth-backend@0.18.5-next.0 - @backstage/plugin-auth-node@0.2.16-next.0 - - @backstage/plugin-azure-devops-backend@0.3.26-next.0 - - @backstage/plugin-azure-sites-backend@0.1.9-next.0 - - @backstage/plugin-badges-backend@0.2.2-next.0 - - @backstage/plugin-code-coverage-backend@0.2.13-next.0 - - @backstage/plugin-devtools-backend@0.1.2-next.0 - - @backstage/plugin-events-backend@0.2.8-next.0 - - @backstage/plugin-events-node@0.2.8-next.0 - - @backstage/plugin-explore-backend@0.0.9-next.0 - - @backstage/plugin-graphql-backend@0.1.37-next.0 - - @backstage/plugin-jenkins-backend@0.2.2-next.0 - - @backstage/plugin-kafka-backend@0.2.40-next.0 - - @backstage/plugin-lighthouse-backend@0.2.3-next.0 - - @backstage/plugin-linguist-backend@0.3.1-next.0 - - @backstage/plugin-nomad-backend@0.1.1-next.0 - @backstage/plugin-permission-backend@0.5.22-next.0 - @backstage/plugin-permission-common@0.7.7-next.0 - @backstage/plugin-permission-node@0.7.10-next.0 - - @backstage/plugin-playlist-backend@0.3.3-next.0 - - @backstage/plugin-proxy-backend@0.2.41-next.0 - - @backstage/plugin-rollbar-backend@0.1.44-next.0 - @backstage/plugin-scaffolder-backend@1.15.1-next.0 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.4-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.16-next.0 - @backstage/plugin-search-backend@1.3.3-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.2-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.8-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.3-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.3-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.3-next.0 - @backstage/plugin-search-backend-node@1.2.3-next.0 - - @backstage/plugin-search-common@1.2.5-next.0 - - @backstage/plugin-tech-insights-backend@0.5.13-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.31-next.0 - - @backstage/plugin-tech-insights-node@0.4.5-next.0 - @backstage/plugin-techdocs-backend@1.6.4-next.0 - @backstage/plugin-todo-backend@0.1.44-next.0 -## 0.2.84 +## 0.0.12 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.19.0 - - @backstage/catalog-client@1.4.2 - - @backstage/plugin-jenkins-backend@0.2.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3 - - @backstage/plugin-devtools-backend@0.1.1 - @backstage/plugin-scaffolder-backend@1.15.0 - - @backstage/plugin-nomad-backend@0.1.0 - - @backstage/plugin-azure-sites-backend@0.1.8 - @backstage/plugin-kubernetes-backend@0.11.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0 - - @backstage/plugin-badges-backend@0.2.1 - @backstage/plugin-catalog-backend@1.10.0 - - @backstage/integration@1.5.0 - @backstage/plugin-search-backend@1.3.2 - - @backstage/plugin-explore-backend@0.0.8 - - @backstage/catalog-model@1.4.0 - - @backstage/plugin-auth-backend@0.18.4 - - @backstage/plugin-adr-backend@0.3.4 - - @backstage/plugin-code-coverage-backend@0.2.12 - - @backstage/plugin-proxy-backend@0.2.40 - - @backstage/plugin-linguist-backend@0.3.0 - - @backstage/plugin-search-backend-module-pg@0.5.7 - - example-app@0.2.84 - - @backstage/backend-tasks@0.5.3 + - @backstage/plugin-search-backend-module-explore@0.1.2 + - @backstage/backend-defaults@0.1.11 - @backstage/plugin-app-backend@0.3.46 - @backstage/plugin-auth-node@0.2.15 - - @backstage/plugin-azure-devops-backend@0.3.25 - - @backstage/plugin-catalog-node@1.3.7 - - @backstage/plugin-entity-feedback-backend@0.1.4 - - @backstage/plugin-events-backend@0.2.7 - - @backstage/plugin-graphql-backend@0.1.36 - - @backstage/plugin-kafka-backend@0.2.39 - - @backstage/plugin-lighthouse-backend@0.2.2 - @backstage/plugin-permission-backend@0.5.21 - @backstage/plugin-permission-node@0.7.9 - - @backstage/plugin-playlist-backend@0.3.2 - - @backstage/plugin-rollbar-backend@0.1.43 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.15 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.1 + - @backstage/plugin-search-backend-module-catalog@0.1.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.2 - @backstage/plugin-search-backend-node@1.2.2 - - @backstage/plugin-tech-insights-backend@0.5.12 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30 - - @backstage/plugin-tech-insights-node@0.4.4 - @backstage/plugin-techdocs-backend@1.6.3 - @backstage/plugin-todo-backend@0.1.43 - - @backstage/config@1.0.8 - - @backstage/plugin-events-node@0.2.7 - @backstage/plugin-permission-common@0.7.6 - - @backstage/plugin-search-common@1.2.4 -## 0.2.84-next.3 +## 0.0.12-next.3 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.15.0-next.3 - @backstage/plugin-kubernetes-backend@0.11.1-next.3 - - @backstage/backend-common@0.19.0-next.2 - - @backstage/catalog-model@1.4.0-next.1 - @backstage/plugin-catalog-backend@1.10.0-next.2 - - example-app@0.2.84-next.3 - - @backstage/backend-tasks@0.5.3-next.2 - - @backstage/catalog-client@1.4.2-next.2 - - @backstage/config@1.0.7 - - @backstage/integration@1.5.0-next.0 - - @backstage/plugin-adr-backend@0.3.4-next.2 + - @backstage/backend-defaults@0.1.11-next.2 - @backstage/plugin-app-backend@0.3.46-next.2 - - @backstage/plugin-auth-backend@0.18.4-next.3 - @backstage/plugin-auth-node@0.2.15-next.2 - - @backstage/plugin-azure-devops-backend@0.3.25-next.2 - - @backstage/plugin-azure-sites-backend@0.1.8-next.2 - - @backstage/plugin-badges-backend@0.2.1-next.3 - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0-next.1 - - @backstage/plugin-catalog-node@1.3.7-next.2 - - @backstage/plugin-code-coverage-backend@0.2.12-next.3 - - @backstage/plugin-devtools-backend@0.1.1-next.2 - - @backstage/plugin-entity-feedback-backend@0.1.4-next.2 - - @backstage/plugin-events-backend@0.2.7-next.2 - - @backstage/plugin-events-node@0.2.7-next.2 - - @backstage/plugin-explore-backend@0.0.8-next.2 - - @backstage/plugin-graphql-backend@0.1.36-next.2 - - @backstage/plugin-jenkins-backend@0.2.1-next.2 - - @backstage/plugin-kafka-backend@0.2.39-next.2 - - @backstage/plugin-lighthouse-backend@0.2.2-next.2 - - @backstage/plugin-linguist-backend@0.3.0-next.2 - @backstage/plugin-permission-backend@0.5.21-next.2 - @backstage/plugin-permission-common@0.7.6-next.0 - @backstage/plugin-permission-node@0.7.9-next.2 - - @backstage/plugin-playlist-backend@0.3.2-next.2 - - @backstage/plugin-proxy-backend@0.2.40-next.2 - - @backstage/plugin-rollbar-backend@0.1.43-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3-next.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.3 - @backstage/plugin-search-backend@1.3.2-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.1-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.7-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.2-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.2-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.2-next.2 - @backstage/plugin-search-backend-node@1.2.2-next.2 - - @backstage/plugin-search-common@1.2.4-next.0 - - @backstage/plugin-tech-insights-backend@0.5.12-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30-next.2 - - @backstage/plugin-tech-insights-node@0.4.4-next.2 - @backstage/plugin-techdocs-backend@1.6.3-next.2 - @backstage/plugin-todo-backend@0.1.43-next.2 -## 0.2.84-next.2 +## 0.0.12-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-kubernetes-backend@0.11.1-next.2 - - @backstage/plugin-badges-backend@0.2.1-next.2 - @backstage/plugin-scaffolder-backend@1.15.0-next.2 - - @backstage/plugin-auth-backend@0.18.4-next.2 - - @backstage/plugin-code-coverage-backend@0.2.12-next.2 - - example-app@0.2.84-next.2 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.2 - - @backstage/config@1.0.7 -## 0.2.84-next.1 +## 0.0.12-next.1 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.19.0-next.1 - - @backstage/plugin-jenkins-backend@0.2.1-next.1 - - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.1.3-next.1 - - @backstage/plugin-devtools-backend@0.1.1-next.1 - @backstage/plugin-catalog-backend-module-unprocessed@0.1.0-next.0 - @backstage/plugin-catalog-backend@1.9.2-next.1 - - @backstage/integration@1.5.0-next.0 - - @backstage/plugin-adr-backend@0.3.4-next.1 - - @backstage/plugin-proxy-backend@0.2.40-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.7-next.1 - - @backstage/catalog-model@1.4.0-next.0 - @backstage/plugin-scaffolder-backend@1.15.0-next.1 - - @backstage/backend-tasks@0.5.3-next.1 + - @backstage/backend-defaults@0.1.11-next.1 - @backstage/plugin-app-backend@0.3.46-next.1 - - @backstage/plugin-auth-backend@0.18.4-next.1 - @backstage/plugin-auth-node@0.2.15-next.1 - - @backstage/plugin-azure-devops-backend@0.3.25-next.1 - - @backstage/plugin-azure-sites-backend@0.1.8-next.1 - - @backstage/plugin-badges-backend@0.2.1-next.1 - - @backstage/plugin-catalog-node@1.3.7-next.1 - - @backstage/plugin-code-coverage-backend@0.2.12-next.1 - - @backstage/plugin-entity-feedback-backend@0.1.4-next.1 - - @backstage/plugin-events-backend@0.2.7-next.1 - - @backstage/plugin-explore-backend@0.0.8-next.1 - - @backstage/plugin-graphql-backend@0.1.36-next.1 - - @backstage/plugin-kafka-backend@0.2.39-next.1 - @backstage/plugin-kubernetes-backend@0.11.1-next.1 - - @backstage/plugin-lighthouse-backend@0.2.2-next.1 - - @backstage/plugin-linguist-backend@0.3.0-next.1 - @backstage/plugin-permission-backend@0.5.21-next.1 - @backstage/plugin-permission-node@0.7.9-next.1 - - @backstage/plugin-playlist-backend@0.3.2-next.1 - - @backstage/plugin-rollbar-backend@0.1.43-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.1 - @backstage/plugin-search-backend@1.3.2-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.1-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.2-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.2-next.1 - @backstage/plugin-search-backend-node@1.2.2-next.1 - - @backstage/plugin-tech-insights-backend@0.5.12-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30-next.1 - - @backstage/plugin-tech-insights-node@0.4.4-next.1 - @backstage/plugin-techdocs-backend@1.6.3-next.1 - @backstage/plugin-todo-backend@0.1.43-next.1 - - example-app@0.2.84-next.1 - - @backstage/catalog-client@1.4.2-next.1 - @backstage/plugin-permission-common@0.7.6-next.0 - - @backstage/plugin-events-node@0.2.7-next.1 - - @backstage/config@1.0.7 - - @backstage/plugin-search-common@1.2.4-next.0 -## 0.2.84-next.0 +## 0.0.12-next.0 ### Patch Changes - Updated dependencies - - @backstage/catalog-client@1.4.2-next.0 - @backstage/plugin-scaffolder-backend@1.14.1-next.0 - - @backstage/plugin-jenkins-backend@0.2.1-next.0 - - @backstage/plugin-linguist-backend@0.3.0-next.0 - - @backstage/plugin-devtools-backend@0.1.1-next.0 - - @backstage/plugin-adr-backend@0.3.4-next.0 - - @backstage/plugin-auth-backend@0.18.4-next.0 - - @backstage/plugin-badges-backend@0.2.1-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.2-next.0 - @backstage/plugin-catalog-backend@1.9.2-next.0 - - @backstage/plugin-catalog-node@1.3.7-next.0 - - @backstage/plugin-code-coverage-backend@0.2.12-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.4-next.0 - @backstage/plugin-kubernetes-backend@0.11.1-next.0 - - @backstage/plugin-lighthouse-backend@0.2.2-next.0 - - @backstage/plugin-playlist-backend@0.3.2-next.0 - - @backstage/plugin-tech-insights-backend@0.5.12-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.2-next.0 - @backstage/plugin-techdocs-backend@1.6.3-next.0 - @backstage/plugin-todo-backend@0.1.43-next.0 - - example-app@0.2.84-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.15-next.0 - - @backstage/backend-common@0.18.6-next.0 - - @backstage/integration@1.4.5 - @backstage/plugin-app-backend@0.3.46-next.0 - - @backstage/plugin-explore-backend@0.0.8-next.0 - - @backstage/config@1.0.7 - - @backstage/backend-tasks@0.5.3-next.0 - - @backstage/catalog-model@1.3.0 + - @backstage/backend-defaults@0.1.11-next.0 - @backstage/plugin-auth-node@0.2.15-next.0 - - @backstage/plugin-azure-devops-backend@0.3.25-next.0 - - @backstage/plugin-azure-sites-backend@0.1.8-next.0 - - @backstage/plugin-events-backend@0.2.7-next.0 - - @backstage/plugin-events-node@0.2.7-next.0 - - @backstage/plugin-graphql-backend@0.1.36-next.0 - - @backstage/plugin-kafka-backend@0.2.39-next.0 - @backstage/plugin-permission-backend@0.5.21-next.0 - @backstage/plugin-permission-common@0.7.5 - @backstage/plugin-permission-node@0.7.9-next.0 - - @backstage/plugin-proxy-backend@0.2.40-next.0 - - @backstage/plugin-rollbar-backend@0.1.43-next.0 - @backstage/plugin-search-backend@1.3.2-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.1-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.7-next.0 - @backstage/plugin-search-backend-node@1.2.2-next.0 - - @backstage/plugin-search-common@1.2.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.30-next.0 - - @backstage/plugin-tech-insights-node@0.4.4-next.0 -## 0.2.83 +## 0.0.11 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.14.0 - - @backstage/plugin-devtools-backend@0.1.0 - @backstage/plugin-catalog-backend@1.9.1 - - @backstage/backend-common@0.18.5 - @backstage/plugin-kubernetes-backend@0.11.0 - - @backstage/plugin-auth-backend@0.18.3 - - @backstage/plugin-badges-backend@0.2.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.0 - - @backstage/integration@1.4.5 - @backstage/plugin-todo-backend@0.1.42 - - @backstage/plugin-jenkins-backend@0.2.0 - - @backstage/plugin-azure-sites-backend@0.1.7 - @backstage/plugin-permission-node@0.7.8 - @backstage/plugin-search-backend@1.3.1 - - example-app@0.2.83 - - @backstage/backend-tasks@0.5.2 + - @backstage/backend-defaults@0.1.10 - @backstage/plugin-app-backend@0.3.45 - @backstage/plugin-auth-node@0.2.14 - - @backstage/plugin-catalog-node@1.3.6 - - @backstage/plugin-entity-feedback-backend@0.1.3 - - @backstage/plugin-events-backend@0.2.6 - - @backstage/plugin-playlist-backend@0.3.1 - - @backstage/plugin-rollbar-backend@0.1.42 - - @backstage/plugin-search-backend-module-pg@0.5.6 - - @backstage/plugin-tech-insights-backend@0.5.11 + - @backstage/plugin-search-backend-module-catalog@0.1.1 + - @backstage/plugin-search-backend-module-explore@0.1.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.1 - @backstage/plugin-techdocs-backend@1.6.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.14 - - @backstage/plugin-adr-backend@0.3.3 - - @backstage/plugin-azure-devops-backend@0.3.24 - - @backstage/plugin-code-coverage-backend@0.2.11 - - @backstage/plugin-explore-backend@0.0.7 - - @backstage/plugin-graphql-backend@0.1.35 - - @backstage/plugin-kafka-backend@0.2.38 - - @backstage/plugin-lighthouse-backend@0.2.1 - - @backstage/plugin-linguist-backend@0.2.2 - @backstage/plugin-permission-backend@0.5.20 - - @backstage/plugin-proxy-backend@0.2.39 - @backstage/plugin-search-backend-node@1.2.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.29 - - @backstage/plugin-tech-insights-node@0.4.3 - - @backstage/catalog-client@1.4.1 - - @backstage/catalog-model@1.3.0 - - @backstage/config@1.0.7 - - @backstage/plugin-events-node@0.2.6 - @backstage/plugin-permission-common@0.7.5 - - @backstage/plugin-search-common@1.2.3 -## 0.2.83-next.2 +## 0.0.11-next.2 ### Patch Changes - Updated dependencies - - @backstage/plugin-devtools-backend@0.1.0-next.0 - @backstage/plugin-catalog-backend@1.9.1-next.2 - - @backstage/plugin-badges-backend@0.2.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.3.0-next.2 - @backstage/plugin-kubernetes-backend@0.11.0-next.2 - - @backstage/plugin-auth-backend@0.18.3-next.2 - @backstage/plugin-search-backend@1.3.1-next.2 - - example-app@0.2.83-next.2 - @backstage/plugin-scaffolder-backend@1.13.2-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.2 - - @backstage/config@1.0.7 -## 0.2.83-next.1 +## 0.0.11-next.1 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.18.5-next.1 - @backstage/plugin-kubernetes-backend@0.11.0-next.1 - @backstage/plugin-catalog-backend@1.9.1-next.1 - - @backstage/plugin-jenkins-backend@0.1.35-next.1 - @backstage/plugin-scaffolder-backend@1.13.2-next.1 - - example-app@0.2.83-next.1 - - @backstage/backend-tasks@0.5.2-next.1 - - @backstage/plugin-adr-backend@0.3.3-next.1 + - @backstage/backend-defaults@0.1.10-next.1 - @backstage/plugin-app-backend@0.3.45-next.1 - - @backstage/plugin-auth-backend@0.18.3-next.1 - @backstage/plugin-auth-node@0.2.14-next.1 - - @backstage/plugin-azure-devops-backend@0.3.24-next.1 - - @backstage/plugin-azure-sites-backend@0.1.7-next.1 - - @backstage/plugin-badges-backend@0.1.39-next.1 - - @backstage/plugin-catalog-node@1.3.6-next.1 - - @backstage/plugin-code-coverage-backend@0.2.11-next.1 - - @backstage/plugin-entity-feedback-backend@0.1.3-next.1 - - @backstage/plugin-events-backend@0.2.6-next.1 - - @backstage/plugin-explore-backend@0.0.7-next.1 - - @backstage/plugin-graphql-backend@0.1.35-next.1 - - @backstage/plugin-kafka-backend@0.2.38-next.1 - - @backstage/plugin-lighthouse-backend@0.2.1-next.1 - - @backstage/plugin-linguist-backend@0.2.2-next.1 - @backstage/plugin-permission-backend@0.5.20-next.1 - @backstage/plugin-permission-node@0.7.8-next.1 - - @backstage/plugin-playlist-backend@0.3.1-next.1 - - @backstage/plugin-proxy-backend@0.2.39-next.1 - - @backstage/plugin-rollbar-backend@0.1.42-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.1 - @backstage/plugin-search-backend@1.3.1-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.2.1-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.6-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.1-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.1-next.1 - @backstage/plugin-search-backend-node@1.2.1-next.1 - - @backstage/plugin-tech-insights-backend@0.5.11-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.29-next.1 - - @backstage/plugin-tech-insights-node@0.4.3-next.1 - @backstage/plugin-techdocs-backend@1.6.2-next.1 - @backstage/plugin-todo-backend@0.1.42-next.1 - - @backstage/config@1.0.7 - - @backstage/plugin-events-node@0.2.6-next.1 -## 0.2.83-next.0 +## 0.0.11-next.0 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.18.5-next.0 - - @backstage/integration@1.4.5-next.0 - @backstage/plugin-permission-node@0.7.8-next.0 - @backstage/plugin-scaffolder-backend@1.13.2-next.0 - @backstage/plugin-kubernetes-backend@0.11.0-next.0 - - @backstage/backend-tasks@0.5.2-next.0 + - @backstage/backend-defaults@0.1.10-next.0 - @backstage/plugin-app-backend@0.3.45-next.0 - - @backstage/plugin-auth-backend@0.18.3-next.0 - @backstage/plugin-auth-node@0.2.14-next.0 - @backstage/plugin-catalog-backend@1.9.1-next.0 - - @backstage/plugin-catalog-node@1.3.6-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.3-next.0 - - @backstage/plugin-events-backend@0.2.6-next.0 - - @backstage/plugin-playlist-backend@0.3.1-next.0 - - @backstage/plugin-rollbar-backend@0.1.42-next.0 - @backstage/plugin-search-backend@1.3.1-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.6-next.0 - - @backstage/plugin-tech-insights-backend@0.5.11-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.1-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.1-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.1-next.0 - @backstage/plugin-techdocs-backend@1.6.2-next.0 - - example-app@0.2.83-next.0 - - @backstage/plugin-adr-backend@0.3.3-next.0 - - @backstage/plugin-azure-devops-backend@0.3.24-next.0 - - @backstage/plugin-azure-sites-backend@0.1.7-next.0 - - @backstage/plugin-badges-backend@0.1.39-next.0 - - @backstage/plugin-code-coverage-backend@0.2.11-next.0 - - @backstage/plugin-explore-backend@0.0.7-next.0 - - @backstage/plugin-graphql-backend@0.1.35-next.0 - - @backstage/plugin-jenkins-backend@0.1.35-next.0 - - @backstage/plugin-kafka-backend@0.2.38-next.0 - - @backstage/plugin-lighthouse-backend@0.2.1-next.0 - - @backstage/plugin-linguist-backend@0.2.2-next.0 - @backstage/plugin-permission-backend@0.5.20-next.0 - - @backstage/plugin-proxy-backend@0.2.39-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.14-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.2.1-next.0 - @backstage/plugin-search-backend-node@1.2.1-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.29-next.0 - - @backstage/plugin-tech-insights-node@0.4.3-next.0 - @backstage/plugin-todo-backend@0.1.42-next.0 - - @backstage/catalog-client@1.4.1 - - @backstage/catalog-model@1.3.0 - - @backstage/config@1.0.7 - - @backstage/plugin-events-node@0.2.6-next.0 - @backstage/plugin-permission-common@0.7.5 - - @backstage/plugin-search-common@1.2.3 -## 0.2.82 +## 0.0.10 ### Patch Changes - Updated dependencies - @backstage/plugin-kubernetes-backend@0.10.0 - - @backstage/backend-common@0.18.4 - @backstage/plugin-scaffolder-backend@1.13.0 - @backstage/plugin-catalog-backend@1.9.0 - - @backstage/plugin-code-coverage-backend@0.2.10 - - @backstage/catalog-client@1.4.1 - @backstage/plugin-permission-node@0.7.7 - @backstage/plugin-permission-backend@0.5.19 - - @backstage/plugin-entity-feedback-backend@0.1.2 - - @backstage/plugin-rollbar-backend@0.1.41 - @backstage/plugin-search-backend@1.3.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.2.0 - - @backstage/plugin-search-backend-module-pg@0.5.5 - - @backstage/plugin-auth-backend@0.18.2 - - @backstage/plugin-lighthouse-backend@0.2.0 - @backstage/plugin-permission-common@0.7.5 - - @backstage/plugin-playlist-backend@0.3.0 - - @backstage/backend-tasks@0.5.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28 - - @backstage/plugin-adr-backend@0.3.2 - - @backstage/plugin-graphql-backend@0.1.34 - - @backstage/catalog-model@1.3.0 - @backstage/plugin-techdocs-backend@1.6.1 - - @backstage/plugin-explore-backend@0.0.6 - - @backstage/plugin-events-backend@0.2.5 - @backstage/plugin-search-backend-node@1.2.0 - - @backstage/integration@1.4.4 - - example-app@0.2.82 + - @backstage/plugin-search-backend-module-techdocs@0.1.0 + - @backstage/plugin-search-backend-module-catalog@0.1.0 + - @backstage/plugin-search-backend-module-explore@0.1.0 + - @backstage/backend-defaults@0.1.9 - @backstage/plugin-app-backend@0.3.44 - @backstage/plugin-auth-node@0.2.13 - - @backstage/plugin-azure-devops-backend@0.3.23 - - @backstage/plugin-azure-sites-backend@0.1.6 - - @backstage/plugin-badges-backend@0.1.38 - - @backstage/plugin-catalog-node@1.3.5 - - @backstage/plugin-jenkins-backend@0.1.34 - - @backstage/plugin-kafka-backend@0.2.37 - - @backstage/plugin-linguist-backend@0.2.1 - - @backstage/plugin-proxy-backend@0.2.38 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.12 - - @backstage/plugin-tech-insights-backend@0.5.10 - - @backstage/plugin-tech-insights-node@0.4.2 - @backstage/plugin-todo-backend@0.1.41 - - @backstage/config@1.0.7 - - @backstage/plugin-events-node@0.2.5 - - @backstage/plugin-search-common@1.2.3 -## 0.2.82-next.3 +## 0.0.10-next.3 ### Patch Changes - Updated dependencies - @backstage/plugin-kubernetes-backend@0.10.0-next.3 - @backstage/plugin-catalog-backend@1.9.0-next.3 - - @backstage/plugin-code-coverage-backend@0.2.10-next.3 - - @backstage/plugin-lighthouse-backend@0.2.0-next.3 - - @backstage/plugin-auth-backend@0.18.2-next.3 - @backstage/plugin-scaffolder-backend@1.13.0-next.3 - - @backstage/plugin-search-backend-module-elasticsearch@1.2.0-next.3 - - @backstage/catalog-model@1.3.0-next.0 - - @backstage/plugin-events-backend@0.2.5-next.3 - - example-app@0.2.82-next.3 - - @backstage/backend-common@0.18.4-next.2 - - @backstage/backend-tasks@0.5.1-next.2 - - @backstage/catalog-client@1.4.1-next.1 - - @backstage/config@1.0.7 - - @backstage/integration@1.4.4-next.0 - - @backstage/plugin-adr-backend@0.3.2-next.3 + - @backstage/backend-defaults@0.1.9-next.2 - @backstage/plugin-app-backend@0.3.44-next.2 - @backstage/plugin-auth-node@0.2.13-next.2 - - @backstage/plugin-azure-devops-backend@0.3.23-next.2 - - @backstage/plugin-azure-sites-backend@0.1.6-next.2 - - @backstage/plugin-badges-backend@0.1.38-next.3 - - @backstage/plugin-catalog-node@1.3.5-next.3 - - @backstage/plugin-entity-feedback-backend@0.1.2-next.3 - - @backstage/plugin-events-node@0.2.5-next.2 - - @backstage/plugin-explore-backend@0.0.6-next.2 - - @backstage/plugin-graphql-backend@0.1.34-next.3 - - @backstage/plugin-jenkins-backend@0.1.34-next.3 - - @backstage/plugin-kafka-backend@0.2.37-next.3 - - @backstage/plugin-linguist-backend@0.2.1-next.3 - @backstage/plugin-permission-backend@0.5.19-next.2 - @backstage/plugin-permission-common@0.7.5-next.0 - @backstage/plugin-permission-node@0.7.7-next.2 - - @backstage/plugin-playlist-backend@0.2.7-next.3 - - @backstage/plugin-proxy-backend@0.2.38-next.2 - - @backstage/plugin-rollbar-backend@0.1.41-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.3 - @backstage/plugin-search-backend@1.3.0-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.5-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.0-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.0-next.2 - @backstage/plugin-search-backend-node@1.2.0-next.2 - - @backstage/plugin-search-common@1.2.3-next.0 - - @backstage/plugin-tech-insights-backend@0.5.10-next.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.2 - - @backstage/plugin-tech-insights-node@0.4.2-next.2 - @backstage/plugin-techdocs-backend@1.6.1-next.3 - @backstage/plugin-todo-backend@0.1.41-next.3 -## 0.2.82-next.2 +## 0.0.10-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-kubernetes-backend@0.10.0-next.2 - @backstage/plugin-catalog-backend@1.8.1-next.2 - - @backstage/backend-common@0.18.4-next.2 - - @backstage/catalog-client@1.4.1-next.0 - @backstage/plugin-permission-node@0.7.7-next.2 - @backstage/plugin-permission-backend@0.5.19-next.2 - - @backstage/plugin-rollbar-backend@0.1.41-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.2 - @backstage/plugin-scaffolder-backend@1.13.0-next.2 - - example-app@0.2.82-next.2 - - @backstage/backend-tasks@0.5.1-next.2 - - @backstage/catalog-model@1.2.1 - - @backstage/config@1.0.7 - - @backstage/integration@1.4.4-next.0 - - @backstage/plugin-adr-backend@0.3.2-next.2 + - @backstage/backend-defaults@0.1.9-next.2 - @backstage/plugin-app-backend@0.3.44-next.2 - - @backstage/plugin-auth-backend@0.18.2-next.2 - @backstage/plugin-auth-node@0.2.13-next.2 - - @backstage/plugin-azure-devops-backend@0.3.23-next.2 - - @backstage/plugin-azure-sites-backend@0.1.6-next.2 - - @backstage/plugin-badges-backend@0.1.38-next.2 - - @backstage/plugin-catalog-node@1.3.5-next.2 - - @backstage/plugin-code-coverage-backend@0.2.10-next.2 - - @backstage/plugin-entity-feedback-backend@0.1.2-next.2 - - @backstage/plugin-events-backend@0.2.5-next.2 - - @backstage/plugin-events-node@0.2.5-next.2 - - @backstage/plugin-explore-backend@0.0.6-next.2 - - @backstage/plugin-graphql-backend@0.1.34-next.2 - - @backstage/plugin-jenkins-backend@0.1.34-next.2 - - @backstage/plugin-kafka-backend@0.2.37-next.2 - - @backstage/plugin-lighthouse-backend@0.1.2-next.2 - - @backstage/plugin-linguist-backend@0.2.1-next.2 - @backstage/plugin-permission-common@0.7.5-next.0 - - @backstage/plugin-playlist-backend@0.2.7-next.2 - - @backstage/plugin-proxy-backend@0.2.38-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.2 - @backstage/plugin-search-backend@1.3.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.2.0-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.5-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.0-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.0-next.1 - @backstage/plugin-search-backend-node@1.2.0-next.2 - - @backstage/plugin-search-common@1.2.3-next.0 - - @backstage/plugin-tech-insights-backend@0.5.10-next.2 - - @backstage/plugin-tech-insights-node@0.4.2-next.2 - @backstage/plugin-techdocs-backend@1.6.1-next.2 - @backstage/plugin-todo-backend@0.1.41-next.2 -## 0.2.82-next.1 +## 0.0.10-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-search-backend@1.3.0-next.1 - @backstage/plugin-scaffolder-backend@1.13.0-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.2.0-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.5-next.1 - - @backstage/plugin-permission-node@0.7.7-next.1 - - @backstage/plugin-permission-backend@0.5.19-next.1 - - @backstage/plugin-permission-common@0.7.5-next.0 - - @backstage/plugin-playlist-backend@0.2.7-next.1 - @backstage/plugin-catalog-backend@1.8.1-next.1 - - @backstage/backend-tasks@0.5.1-next.1 - - @backstage/plugin-adr-backend@0.3.2-next.1 - @backstage/plugin-kubernetes-backend@0.10.0-next.1 - @backstage/plugin-techdocs-backend@1.6.1-next.1 - - @backstage/plugin-explore-backend@0.0.6-next.1 - @backstage/plugin-search-backend-node@1.2.0-next.1 - - @backstage/integration@1.4.4-next.0 - - @backstage/plugin-auth-backend@0.18.2-next.1 - - example-app@0.2.82-next.1 - - @backstage/backend-common@0.18.4-next.1 - - @backstage/catalog-client@1.4.0 - - @backstage/catalog-model@1.2.1 - - @backstage/config@1.0.7 + - @backstage/plugin-search-backend-module-techdocs@0.1.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.0-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.0-next.0 + - @backstage/backend-defaults@0.1.9-next.1 - @backstage/plugin-app-backend@0.3.44-next.1 - - @backstage/plugin-auth-node@0.2.13-next.1 - - @backstage/plugin-azure-devops-backend@0.3.23-next.1 - - @backstage/plugin-azure-sites-backend@0.1.6-next.1 - - @backstage/plugin-badges-backend@0.1.38-next.1 - - @backstage/plugin-catalog-node@1.3.5-next.1 - - @backstage/plugin-code-coverage-backend@0.2.10-next.1 - - @backstage/plugin-entity-feedback-backend@0.1.2-next.1 - - @backstage/plugin-events-backend@0.2.5-next.1 - - @backstage/plugin-events-node@0.2.5-next.1 - - @backstage/plugin-graphql-backend@0.1.34-next.1 - - @backstage/plugin-jenkins-backend@0.1.34-next.1 - - @backstage/plugin-kafka-backend@0.2.37-next.1 - - @backstage/plugin-lighthouse-backend@0.1.2-next.1 - - @backstage/plugin-linguist-backend@0.2.1-next.1 - - @backstage/plugin-proxy-backend@0.2.38-next.1 - - @backstage/plugin-rollbar-backend@0.1.41-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.1 - - @backstage/plugin-search-common@1.2.3-next.0 - - @backstage/plugin-tech-insights-backend@0.5.10-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.1 - - @backstage/plugin-tech-insights-node@0.4.2-next.1 - @backstage/plugin-todo-backend@0.1.41-next.1 -## 0.2.82-next.0 +## 0.0.10-next.0 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.12.1-next.0 - - @backstage/plugin-kubernetes-backend@0.10.0-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.2-next.0 - - @backstage/plugin-auth-backend@0.18.2-next.0 - @backstage/plugin-catalog-backend@1.8.1-next.0 - - @backstage/plugin-graphql-backend@0.1.34-next.0 - - @backstage/backend-common@0.18.4-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.12-next.0 - - example-app@0.2.82-next.0 - - @backstage/config@1.0.7 - - @backstage/integration@1.4.3 - - @backstage/backend-tasks@0.5.1-next.0 - - @backstage/catalog-client@1.4.0 - - @backstage/catalog-model@1.2.1 - - @backstage/plugin-adr-backend@0.3.2-next.0 + - @backstage/backend-defaults@0.1.9-next.0 - @backstage/plugin-app-backend@0.3.44-next.0 - - @backstage/plugin-auth-node@0.2.13-next.0 - - @backstage/plugin-azure-devops-backend@0.3.23-next.0 - - @backstage/plugin-azure-sites-backend@0.1.6-next.0 - - @backstage/plugin-badges-backend@0.1.38-next.0 - - @backstage/plugin-catalog-node@1.3.5-next.0 - - @backstage/plugin-code-coverage-backend@0.2.10-next.0 - - @backstage/plugin-events-backend@0.2.5-next.0 - - @backstage/plugin-events-node@0.2.5-next.0 - - @backstage/plugin-explore-backend@0.0.6-next.0 - - @backstage/plugin-jenkins-backend@0.1.34-next.0 - - @backstage/plugin-kafka-backend@0.2.37-next.0 - - @backstage/plugin-lighthouse-backend@0.1.2-next.0 - - @backstage/plugin-linguist-backend@0.2.1-next.0 - - @backstage/plugin-permission-backend@0.5.19-next.0 - - @backstage/plugin-permission-common@0.7.4 - - @backstage/plugin-permission-node@0.7.7-next.0 - - @backstage/plugin-playlist-backend@0.2.7-next.0 - - @backstage/plugin-proxy-backend@0.2.38-next.0 - - @backstage/plugin-rollbar-backend@0.1.41-next.0 - - @backstage/plugin-search-backend@1.2.5-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.5-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.5-next.0 - - @backstage/plugin-search-backend-node@1.1.5-next.0 - - @backstage/plugin-search-common@1.2.2 - - @backstage/plugin-tech-insights-backend@0.5.10-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.28-next.0 - - @backstage/plugin-tech-insights-node@0.4.2-next.0 - @backstage/plugin-techdocs-backend@1.6.1-next.0 - @backstage/plugin-todo-backend@0.1.41-next.0 -## 0.2.81 +## 0.0.9 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.12.0 - @backstage/plugin-catalog-backend@1.8.0 - - @backstage/catalog-client@1.4.0 - @backstage/plugin-todo-backend@0.1.40 - - @backstage/plugin-permission-node@0.7.6 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.4 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27 - - @backstage/plugin-auth-node@0.2.12 - @backstage/plugin-techdocs-backend@1.6.0 - - @backstage/backend-tasks@0.5.0 - - @backstage/plugin-tech-insights-backend@0.5.9 - - @backstage/plugin-adr-backend@0.3.1 - - @backstage/plugin-auth-backend@0.18.1 - - @backstage/backend-common@0.18.3 - - @backstage/plugin-linguist-backend@0.2.0 - - @backstage/plugin-catalog-node@1.3.4 - - @backstage/catalog-model@1.2.1 - - @backstage/plugin-events-backend@0.2.4 + - @backstage/backend-defaults@0.1.8 - @backstage/plugin-app-backend@0.3.43 - - @backstage/plugin-events-node@0.2.4 - - @backstage/integration@1.4.3 - - @backstage/plugin-azure-devops-backend@0.3.22 - - @backstage/plugin-azure-sites-backend@0.1.5 - - @backstage/plugin-code-coverage-backend@0.2.9 - - @backstage/plugin-entity-feedback-backend@0.1.1 - - @backstage/plugin-explore-backend@0.0.5 - - @backstage/plugin-graphql-backend@0.1.33 - - @backstage/plugin-jenkins-backend@0.1.33 - - @backstage/plugin-kubernetes-backend@0.9.4 - - @backstage/plugin-permission-backend@0.5.18 - - @backstage/plugin-permission-common@0.7.4 - - @backstage/plugin-playlist-backend@0.2.6 - - @backstage/plugin-proxy-backend@0.2.37 - - @backstage/plugin-rollbar-backend@0.1.40 - - @backstage/plugin-lighthouse-backend@0.1.1 - - @backstage/config@1.0.7 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.11 - - @backstage/plugin-badges-backend@0.1.37 - - example-app@0.2.81 - - @backstage/plugin-search-backend@1.2.4 - - @backstage/plugin-kafka-backend@0.2.36 - - @backstage/plugin-search-backend-module-pg@0.5.4 - - @backstage/plugin-search-backend-node@1.1.4 - - @backstage/plugin-search-common@1.2.2 - - @backstage/plugin-tech-insights-node@0.4.1 -## 0.2.81-next.2 +## 0.0.9-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.12.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.4-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27-next.2 - - @backstage/plugin-auth-node@0.2.12-next.2 - - @backstage/backend-tasks@0.5.0-next.2 - - @backstage/plugin-adr-backend@0.3.1-next.2 - - @backstage/backend-common@0.18.3-next.2 - - @backstage/plugin-linguist-backend@0.2.0-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.11-next.2 - - example-app@0.2.81-next.2 - - @backstage/plugin-techdocs-backend@1.5.4-next.2 - - @backstage/plugin-auth-backend@0.18.1-next.2 - - @backstage/plugin-entity-feedback-backend@0.1.1-next.2 - - @backstage/plugin-jenkins-backend@0.1.33-next.2 - - @backstage/plugin-kubernetes-backend@0.9.4-next.2 - - @backstage/plugin-permission-backend@0.5.18-next.2 - - @backstage/plugin-permission-node@0.7.6-next.2 - - @backstage/plugin-playlist-backend@0.2.6-next.2 - - @backstage/plugin-search-backend@1.2.4-next.2 - - @backstage/plugin-lighthouse-backend@0.1.1-next.2 - - @backstage/plugin-search-backend-node@1.1.4-next.2 - - @backstage/plugin-tech-insights-backend@0.5.9-next.2 - - @backstage/plugin-tech-insights-node@0.4.1-next.2 + - @backstage/backend-defaults@0.1.8-next.2 - @backstage/plugin-app-backend@0.3.43-next.2 - - @backstage/plugin-azure-devops-backend@0.3.22-next.2 - - @backstage/plugin-azure-sites-backend@0.1.5-next.2 - - @backstage/plugin-badges-backend@0.1.37-next.2 - @backstage/plugin-catalog-backend@1.8.0-next.2 - - @backstage/plugin-catalog-node@1.3.4-next.2 - - @backstage/plugin-code-coverage-backend@0.2.9-next.2 - - @backstage/plugin-events-backend@0.2.4-next.2 - - @backstage/plugin-explore-backend@0.0.5-next.2 - - @backstage/plugin-graphql-backend@0.1.33-next.2 - - @backstage/plugin-kafka-backend@0.2.36-next.2 - - @backstage/plugin-proxy-backend@0.2.37-next.2 - - @backstage/plugin-rollbar-backend@0.1.40-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.4-next.2 - @backstage/plugin-todo-backend@0.1.40-next.2 - - @backstage/plugin-events-node@0.2.4-next.2 - - @backstage/config@1.0.7-next.0 - - @backstage/integration@1.4.3-next.0 -## 0.2.81-next.1 +## 0.0.9-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.12.0-next.1 - - @backstage/plugin-permission-node@0.7.6-next.1 - - @backstage/plugin-techdocs-backend@1.5.4-next.1 - - @backstage/plugin-auth-backend@0.18.1-next.1 - - @backstage/backend-common@0.18.3-next.1 - - @backstage/catalog-client@1.4.0-next.1 - - @backstage/integration@1.4.3-next.0 - - @backstage/plugin-adr-backend@0.3.1-next.1 - @backstage/plugin-app-backend@0.3.43-next.1 - - @backstage/plugin-auth-node@0.2.12-next.1 - - @backstage/plugin-azure-devops-backend@0.3.22-next.1 - - @backstage/plugin-azure-sites-backend@0.1.5-next.1 - @backstage/plugin-catalog-backend@1.8.0-next.1 - - @backstage/plugin-code-coverage-backend@0.2.9-next.1 - - @backstage/plugin-entity-feedback-backend@0.1.1-next.1 - - @backstage/plugin-explore-backend@0.0.5-next.1 - - @backstage/plugin-graphql-backend@0.1.33-next.1 - - @backstage/plugin-jenkins-backend@0.1.33-next.1 - - @backstage/plugin-kubernetes-backend@0.9.4-next.1 - - @backstage/plugin-linguist-backend@0.2.0-next.1 - - @backstage/plugin-permission-backend@0.5.18-next.1 - - @backstage/plugin-permission-common@0.7.4-next.0 - - @backstage/plugin-playlist-backend@0.2.6-next.1 - - @backstage/plugin-proxy-backend@0.2.37-next.1 - - @backstage/plugin-rollbar-backend@0.1.40-next.1 - @backstage/plugin-todo-backend@0.1.40-next.1 - - @backstage/plugin-lighthouse-backend@0.1.1-next.1 - - @backstage/backend-tasks@0.4.4-next.1 - - @backstage/config@1.0.7-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.11-next.1 - - @backstage/plugin-search-backend@1.2.4-next.1 - - example-app@0.2.81-next.1 - - @backstage/catalog-model@1.2.1-next.1 - - @backstage/plugin-badges-backend@0.1.37-next.1 - - @backstage/plugin-catalog-node@1.3.4-next.1 - - @backstage/plugin-events-backend@0.2.4-next.1 - - @backstage/plugin-events-node@0.2.4-next.1 - - @backstage/plugin-kafka-backend@0.2.36-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.4-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.4-next.1 - - @backstage/plugin-search-backend-node@1.1.4-next.1 - - @backstage/plugin-search-common@1.2.2-next.0 - - @backstage/plugin-tech-insights-backend@0.5.9-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27-next.1 - - @backstage/plugin-tech-insights-node@0.4.1-next.1 + - @backstage/backend-defaults@0.1.8-next.1 -## 0.2.81-next.0 +## 0.0.9-next.0 ### Patch Changes - Updated dependencies - - @backstage/catalog-client@1.4.0-next.0 - @backstage/plugin-todo-backend@0.1.40-next.0 - @backstage/plugin-scaffolder-backend@1.11.1-next.0 - @backstage/plugin-catalog-backend@1.8.0-next.0 - - @backstage/plugin-tech-insights-backend@0.5.9-next.0 - - @backstage/plugin-linguist-backend@0.2.0-next.0 - - @backstage/plugin-adr-backend@0.3.1-next.0 - - @backstage/backend-tasks@0.4.4-next.0 - - @backstage/plugin-techdocs-backend@1.5.4-next.0 - - @backstage/backend-common@0.18.3-next.0 - - @backstage/catalog-model@1.2.1-next.0 - - @backstage/plugin-events-backend@0.2.4-next.0 - - @backstage/plugin-catalog-node@1.3.4-next.0 + - @backstage/backend-defaults@0.1.8-next.0 - @backstage/plugin-app-backend@0.3.43-next.0 - - @backstage/plugin-events-node@0.2.4-next.0 - - @backstage/plugin-proxy-backend@0.2.37-next.0 - - @backstage/plugin-auth-backend@0.18.1-next.0 - - @backstage/plugin-badges-backend@0.1.37-next.0 - - @backstage/plugin-code-coverage-backend@0.2.9-next.0 - - @backstage/plugin-entity-feedback-backend@0.1.1-next.0 - - @backstage/plugin-jenkins-backend@0.1.33-next.0 - - @backstage/plugin-kubernetes-backend@0.9.4-next.0 - - @backstage/plugin-lighthouse-backend@0.1.1-next.0 - - @backstage/plugin-playlist-backend@0.2.6-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.11-next.0 - - example-app@0.2.81-next.0 - - @backstage/config@1.0.6 - - @backstage/integration@1.4.2 - - @backstage/plugin-auth-node@0.2.12-next.0 - - @backstage/plugin-azure-devops-backend@0.3.22-next.0 - - @backstage/plugin-azure-sites-backend@0.1.5-next.0 - - @backstage/plugin-explore-backend@0.0.5-next.0 - - @backstage/plugin-graphql-backend@0.1.33-next.0 - - @backstage/plugin-kafka-backend@0.2.36-next.0 - - @backstage/plugin-permission-backend@0.5.18-next.0 - - @backstage/plugin-permission-common@0.7.3 - - @backstage/plugin-permission-node@0.7.6-next.0 - - @backstage/plugin-rollbar-backend@0.1.40-next.0 - - @backstage/plugin-search-backend@1.2.4-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.4-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.4-next.0 - - @backstage/plugin-search-backend-node@1.1.4-next.0 - - @backstage/plugin-search-common@1.2.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.27-next.0 - - @backstage/plugin-tech-insights-node@0.4.1-next.0 -## 0.2.80 +## 0.0.8 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.7.2 - - @backstage/plugin-playlist-backend@0.2.5 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.3 - - @backstage/backend-common@0.18.2 - - @backstage/plugin-kubernetes-backend@0.9.3 - - @backstage/plugin-lighthouse-backend@0.1.0 - - @backstage/plugin-code-coverage-backend@0.2.8 - - @backstage/plugin-azure-devops-backend@0.3.21 - - @backstage/plugin-azure-sites-backend@0.1.4 - - @backstage/plugin-adr-backend@0.3.0 - - @backstage/plugin-tech-insights-backend@0.5.8 - - @backstage/plugin-tech-insights-node@0.4.0 - - @backstage/plugin-entity-feedback-backend@0.1.0 - - @backstage/plugin-search-backend@1.2.3 - - @backstage/plugin-techdocs-backend@1.5.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.10 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26 - @backstage/plugin-scaffolder-backend@1.11.0 - - @backstage/plugin-events-backend@0.2.3 - - @backstage/plugin-kafka-backend@0.2.35 - - @backstage/plugin-proxy-backend@0.2.36 - @backstage/plugin-app-backend@0.3.42 - - @backstage/catalog-model@1.2.0 - - @backstage/plugin-auth-backend@0.18.0 - - @backstage/plugin-linguist-backend@0.1.0 - - @backstage/plugin-events-node@0.2.3 - - example-app@0.2.80 - - @backstage/plugin-catalog-node@1.3.3 - - @backstage/backend-tasks@0.4.3 - - @backstage/catalog-client@1.3.1 - - @backstage/config@1.0.6 - - @backstage/integration@1.4.2 - - @backstage/plugin-auth-node@0.2.11 - - @backstage/plugin-badges-backend@0.1.36 - - @backstage/plugin-explore-backend@0.0.4 - - @backstage/plugin-graphql-backend@0.1.32 - - @backstage/plugin-jenkins-backend@0.1.32 - - @backstage/plugin-permission-backend@0.5.17 - - @backstage/plugin-permission-common@0.7.3 - - @backstage/plugin-permission-node@0.7.5 - - @backstage/plugin-rollbar-backend@0.1.39 - - @backstage/plugin-search-backend-module-pg@0.5.3 - - @backstage/plugin-search-backend-node@1.1.3 - - @backstage/plugin-search-common@1.2.1 - - @backstage/plugin-todo-backend@0.1.39 + - @backstage/backend-defaults@0.1.7 -## 0.2.80-next.2 +## 0.0.8-next.2 ### Patch Changes - Updated dependencies - - @backstage/plugin-lighthouse-backend@0.1.0-next.0 - - @backstage/backend-common@0.18.2-next.2 - @backstage/plugin-catalog-backend@1.7.2-next.2 - - @backstage/plugin-search-backend@1.2.3-next.2 - @backstage/plugin-scaffolder-backend@1.11.0-next.2 - - @backstage/plugin-events-backend@0.2.3-next.2 - - @backstage/plugin-kafka-backend@0.2.35-next.2 - - @backstage/plugin-proxy-backend@0.2.36-next.2 - @backstage/plugin-app-backend@0.3.42-next.2 - - @backstage/catalog-model@1.2.0-next.1 - - @backstage/plugin-kubernetes-backend@0.9.3-next.2 - - @backstage/plugin-events-node@0.2.3-next.2 - - @backstage/plugin-catalog-node@1.3.3-next.2 - - @backstage/backend-tasks@0.4.3-next.2 - - @backstage/plugin-auth-backend@0.17.5-next.2 - - @backstage/plugin-auth-node@0.2.11-next.2 - - @backstage/plugin-permission-node@0.7.5-next.2 - - @backstage/plugin-playlist-backend@0.2.5-next.2 - - @backstage/plugin-rollbar-backend@0.1.39-next.2 - - @backstage/plugin-search-backend-module-pg@0.5.3-next.2 - - @backstage/plugin-tech-insights-backend@0.5.8-next.2 - - @backstage/plugin-techdocs-backend@1.5.3-next.2 - - example-app@0.2.80-next.2 - - @backstage/catalog-client@1.3.1-next.1 - - @backstage/config@1.0.6 - - @backstage/integration@1.4.2 - - @backstage/plugin-adr-backend@0.2.7-next.2 - - @backstage/plugin-azure-devops-backend@0.3.21-next.2 - - @backstage/plugin-azure-sites-backend@0.1.4-next.2 - - @backstage/plugin-badges-backend@0.1.36-next.2 - - @backstage/plugin-code-coverage-backend@0.2.8-next.2 - - @backstage/plugin-explore-backend@0.0.4-next.2 - - @backstage/plugin-graphql-backend@0.1.32-next.2 - - @backstage/plugin-jenkins-backend@0.1.32-next.2 - - @backstage/plugin-permission-backend@0.5.17-next.2 - - @backstage/plugin-permission-common@0.7.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.10-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.3-next.2 - - @backstage/plugin-search-backend-node@1.1.3-next.2 - - @backstage/plugin-search-common@1.2.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26-next.2 - - @backstage/plugin-tech-insights-node@0.4.0-next.2 - - @backstage/plugin-todo-backend@0.1.39-next.2 + - @backstage/backend-defaults@0.1.7-next.2 -## 0.2.80-next.1 +## 0.0.8-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.7.2-next.1 - - @backstage/plugin-tech-insights-backend@0.5.8-next.1 - - @backstage/plugin-tech-insights-node@0.4.0-next.1 - - @backstage/plugin-azure-devops-backend@0.3.21-next.1 - - @backstage/backend-common@0.18.2-next.1 - - @backstage/plugin-kubernetes-backend@0.9.3-next.1 - @backstage/plugin-scaffolder-backend@1.11.0-next.1 - - @backstage/plugin-playlist-backend@0.2.5-next.1 - - example-app@0.2.80-next.1 - - @backstage/backend-tasks@0.4.3-next.1 - - @backstage/catalog-client@1.3.1-next.0 - - @backstage/catalog-model@1.1.6-next.0 - - @backstage/config@1.0.6 - - @backstage/integration@1.4.2 - - @backstage/plugin-adr-backend@0.2.7-next.1 + - @backstage/backend-defaults@0.1.7-next.1 - @backstage/plugin-app-backend@0.3.42-next.1 - - @backstage/plugin-auth-backend@0.17.5-next.1 - - @backstage/plugin-auth-node@0.2.11-next.1 - - @backstage/plugin-azure-sites-backend@0.1.4-next.1 - - @backstage/plugin-badges-backend@0.1.36-next.1 - - @backstage/plugin-catalog-node@1.3.3-next.1 - - @backstage/plugin-code-coverage-backend@0.2.8-next.1 - - @backstage/plugin-events-backend@0.2.3-next.1 - - @backstage/plugin-events-node@0.2.3-next.1 - - @backstage/plugin-explore-backend@0.0.4-next.1 - - @backstage/plugin-graphql-backend@0.1.32-next.1 - - @backstage/plugin-jenkins-backend@0.1.32-next.1 - - @backstage/plugin-kafka-backend@0.2.35-next.1 - - @backstage/plugin-permission-backend@0.5.17-next.1 - - @backstage/plugin-permission-common@0.7.3 - - @backstage/plugin-permission-node@0.7.5-next.1 - - @backstage/plugin-proxy-backend@0.2.36-next.1 - - @backstage/plugin-rollbar-backend@0.1.39-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.10-next.1 - - @backstage/plugin-search-backend@1.2.3-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.3-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.3-next.1 - - @backstage/plugin-search-backend-node@1.1.3-next.1 - - @backstage/plugin-search-common@1.2.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26-next.1 - - @backstage/plugin-techdocs-backend@1.5.3-next.1 - - @backstage/plugin-todo-backend@0.1.39-next.1 -## 0.2.80-next.0 +## 0.0.8-next.0 ### Patch Changes - Updated dependencies - - @backstage/plugin-kubernetes-backend@0.9.3-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.10-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.26-next.0 - @backstage/plugin-scaffolder-backend@1.11.0-next.0 - - @backstage/catalog-model@1.1.6-next.0 - - example-app@0.2.80-next.0 - - @backstage/plugin-techdocs-backend@1.5.3-next.0 - - @backstage/backend-common@0.18.2-next.0 + - @backstage/backend-defaults@0.1.7-next.0 - @backstage/plugin-app-backend@0.3.42-next.0 - - @backstage/catalog-client@1.3.1-next.0 - - @backstage/plugin-adr-backend@0.2.7-next.0 - - @backstage/plugin-auth-backend@0.17.5-next.0 - - @backstage/plugin-badges-backend@0.1.36-next.0 - @backstage/plugin-catalog-backend@1.7.2-next.0 - - @backstage/plugin-catalog-node@1.3.3-next.0 - - @backstage/plugin-code-coverage-backend@0.2.8-next.0 - - @backstage/plugin-jenkins-backend@0.1.32-next.0 - - @backstage/plugin-kafka-backend@0.2.35-next.0 - - @backstage/plugin-playlist-backend@0.2.5-next.0 - - @backstage/plugin-tech-insights-backend@0.5.8-next.0 - - @backstage/plugin-todo-backend@0.1.39-next.0 - - @backstage/backend-tasks@0.4.3-next.0 - - @backstage/plugin-auth-node@0.2.11-next.0 - - @backstage/plugin-events-backend@0.2.3-next.0 - - @backstage/plugin-permission-node@0.7.5-next.0 - - @backstage/plugin-rollbar-backend@0.1.39-next.0 - - @backstage/plugin-search-backend-module-pg@0.5.3-next.0 - - @backstage/plugin-azure-devops-backend@0.3.21-next.0 - - @backstage/plugin-azure-sites-backend@0.1.4-next.0 - - @backstage/plugin-explore-backend@0.0.4-next.0 - - @backstage/plugin-graphql-backend@0.1.32-next.0 - - @backstage/plugin-permission-backend@0.5.17-next.0 - - @backstage/plugin-proxy-backend@0.2.36-next.0 - - @backstage/plugin-search-backend@1.2.3-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.3-next.0 - - @backstage/plugin-search-backend-node@1.1.3-next.0 - - @backstage/plugin-tech-insights-node@0.3.10-next.0 - - @backstage/plugin-events-node@0.2.3-next.0 -## 0.2.79 +## 0.0.7 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.10.0 - - @backstage/backend-common@0.18.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.8 - - @backstage/plugin-adr-backend@0.2.5 - - @backstage/catalog-model@1.1.5 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.1 - - @backstage/plugin-search-backend-node@1.1.1 - - @backstage/catalog-client@1.3.0 - - @backstage/plugin-explore-backend@0.0.2 - - @backstage/backend-tasks@0.4.1 - - @backstage/plugin-events-backend@0.2.1 - - @backstage/plugin-catalog-node@1.3.1 + - @backstage/backend-defaults@0.1.5 - @backstage/plugin-app-backend@0.3.40 - - @backstage/plugin-code-coverage-backend@0.2.6 - @backstage/plugin-catalog-backend@1.7.0 - - @backstage/plugin-tech-insights-backend@0.5.6 - - @backstage/plugin-kubernetes-backend@0.9.1 - - @backstage/config@1.0.6 - - @backstage/plugin-search-backend@1.2.1 - - @backstage/plugin-events-node@0.2.1 - - example-app@0.2.79 - - @backstage/integration@1.4.2 - - @backstage/plugin-auth-backend@0.17.3 - - @backstage/plugin-auth-node@0.2.9 - - @backstage/plugin-azure-devops-backend@0.3.19 - - @backstage/plugin-azure-sites-backend@0.1.2 - - @backstage/plugin-badges-backend@0.1.34 - - @backstage/plugin-graphql-backend@0.1.30 - - @backstage/plugin-jenkins-backend@0.1.30 - - @backstage/plugin-kafka-backend@0.2.33 - - @backstage/plugin-permission-backend@0.5.15 - - @backstage/plugin-permission-common@0.7.3 - - @backstage/plugin-permission-node@0.7.3 - - @backstage/plugin-playlist-backend@0.2.3 - - @backstage/plugin-proxy-backend@0.2.34 - - @backstage/plugin-rollbar-backend@0.1.37 - - @backstage/plugin-search-backend-module-pg@0.5.1 - - @backstage/plugin-search-common@1.2.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.24 - - @backstage/plugin-tech-insights-node@0.3.8 - - @backstage/plugin-techdocs-backend@1.5.1 - - @backstage/plugin-todo-backend@0.1.37 -## 0.2.79-next.2 +## 0.0.7-next.2 ### Patch Changes - Updated dependencies - - @backstage/plugin-adr-backend@0.2.5-next.2 - - @backstage/backend-common@0.18.0-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.8-next.2 + - @backstage/backend-defaults@0.1.5-next.1 - @backstage/plugin-scaffolder-backend@1.10.0-next.2 - - @backstage/backend-tasks@0.4.1-next.1 - - @backstage/catalog-client@1.3.0-next.2 - @backstage/plugin-catalog-backend@1.7.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.1-next.2 - - @backstage/plugin-events-backend@0.2.1-next.1 - @backstage/plugin-app-backend@0.3.40-next.1 - - @backstage/plugin-kubernetes-backend@0.9.1-next.2 - - @backstage/plugin-catalog-node@1.3.1-next.2 - - @backstage/plugin-events-node@0.2.1-next.1 - - @backstage/plugin-auth-backend@0.17.3-next.2 - - @backstage/plugin-auth-node@0.2.9-next.1 - - @backstage/plugin-permission-node@0.7.3-next.1 - - @backstage/plugin-playlist-backend@0.2.3-next.2 - - @backstage/plugin-rollbar-backend@0.1.37-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.1-next.2 - - @backstage/plugin-tech-insights-backend@0.5.6-next.2 - - @backstage/plugin-techdocs-backend@1.5.1-next.2 - - example-app@0.2.79-next.2 - - @backstage/plugin-azure-devops-backend@0.3.19-next.1 - - @backstage/plugin-azure-sites-backend@0.1.2-next.1 - - @backstage/plugin-badges-backend@0.1.34-next.2 - - @backstage/plugin-code-coverage-backend@0.2.6-next.2 - - @backstage/plugin-explore-backend@0.0.2-next.2 - - @backstage/plugin-graphql-backend@0.1.30-next.2 - - @backstage/plugin-jenkins-backend@0.1.30-next.2 - - @backstage/plugin-kafka-backend@0.2.33-next.2 - - @backstage/plugin-permission-backend@0.5.15-next.1 - - @backstage/plugin-proxy-backend@0.2.34-next.1 - - @backstage/plugin-search-backend@1.2.1-next.2 - - @backstage/plugin-search-backend-node@1.1.1-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.24-next.1 - - @backstage/plugin-tech-insights-node@0.3.8-next.1 - - @backstage/plugin-todo-backend@0.1.37-next.2 - - @backstage/catalog-model@1.1.5-next.1 - - @backstage/config@1.0.6-next.0 - - @backstage/integration@1.4.2-next.0 - - @backstage/plugin-permission-common@0.7.3-next.0 - - @backstage/plugin-search-common@1.2.1-next.0 -## 0.2.79-next.1 +## 0.0.7-next.1 ### Patch Changes - Updated dependencies + - @backstage/backend-defaults@0.1.5-next.0 - @backstage/plugin-scaffolder-backend@1.10.0-next.1 - - @backstage/backend-common@0.18.0-next.0 - - @backstage/plugin-explore-backend@0.0.2-next.1 - - @backstage/plugin-events-backend@0.2.1-next.0 - @backstage/plugin-app-backend@0.3.40-next.0 - - @backstage/plugin-tech-insights-backend@0.5.6-next.1 - - @backstage/config@1.0.6-next.0 - @backstage/plugin-catalog-backend@1.7.0-next.1 - - @backstage/plugin-catalog-node@1.3.1-next.1 - - @backstage/plugin-events-node@0.2.1-next.0 - - example-app@0.2.79-next.1 - - @backstage/backend-tasks@0.4.1-next.0 - - @backstage/catalog-client@1.3.0-next.1 - - @backstage/catalog-model@1.1.5-next.1 - - @backstage/integration@1.4.2-next.0 - - @backstage/plugin-auth-backend@0.17.3-next.1 - - @backstage/plugin-auth-node@0.2.9-next.0 - - @backstage/plugin-azure-devops-backend@0.3.19-next.0 - - @backstage/plugin-azure-sites-backend@0.1.2-next.0 - - @backstage/plugin-badges-backend@0.1.34-next.1 - - @backstage/plugin-code-coverage-backend@0.2.6-next.1 - - @backstage/plugin-graphql-backend@0.1.30-next.1 - - @backstage/plugin-jenkins-backend@0.1.30-next.1 - - @backstage/plugin-kafka-backend@0.2.33-next.1 - - @backstage/plugin-kubernetes-backend@0.9.1-next.1 - - @backstage/plugin-permission-backend@0.5.15-next.0 - - @backstage/plugin-permission-common@0.7.3-next.0 - - @backstage/plugin-permission-node@0.7.3-next.0 - - @backstage/plugin-playlist-backend@0.2.3-next.1 - - @backstage/plugin-proxy-backend@0.2.34-next.0 - - @backstage/plugin-rollbar-backend@0.1.37-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.8-next.1 - - @backstage/plugin-search-backend@1.2.1-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.1-next.1 - - @backstage/plugin-search-backend-module-pg@0.5.1-next.1 - - @backstage/plugin-search-backend-node@1.1.1-next.1 - - @backstage/plugin-search-common@1.2.1-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.24-next.0 - - @backstage/plugin-tech-insights-node@0.3.8-next.0 - - @backstage/plugin-techdocs-backend@1.5.1-next.1 - - @backstage/plugin-todo-backend@0.1.37-next.1 -## 0.2.79-next.0 +## 0.0.7-next.0 ### Patch Changes - Updated dependencies - - @backstage/catalog-model@1.1.5-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.1-next.0 - - @backstage/plugin-search-backend-node@1.1.1-next.0 - - @backstage/catalog-client@1.3.0-next.0 - - @backstage/plugin-explore-backend@0.0.2-next.0 - - @backstage/plugin-code-coverage-backend@0.2.6-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.8-next.0 - @backstage/plugin-scaffolder-backend@1.9.1-next.0 - @backstage/plugin-catalog-backend@1.7.0-next.0 - - @backstage/plugin-search-backend@1.2.1-next.0 - - example-app@0.2.79-next.0 - - @backstage/backend-common@0.17.0 - - @backstage/backend-tasks@0.4.0 - - @backstage/config@1.0.5 - - @backstage/integration@1.4.1 + - @backstage/backend-defaults@0.1.4 - @backstage/plugin-app-backend@0.3.39 - - @backstage/plugin-auth-backend@0.17.3-next.0 - - @backstage/plugin-auth-node@0.2.8 - - @backstage/plugin-azure-devops-backend@0.3.18 - - @backstage/plugin-azure-sites-backend@0.1.1 - - @backstage/plugin-badges-backend@0.1.34-next.0 - - @backstage/plugin-catalog-node@1.3.1-next.0 - - @backstage/plugin-events-backend@0.2.0 - - @backstage/plugin-events-node@0.2.0 - - @backstage/plugin-graphql-backend@0.1.30-next.0 - - @backstage/plugin-jenkins-backend@0.1.30-next.0 - - @backstage/plugin-kafka-backend@0.2.33-next.0 - - @backstage/plugin-kubernetes-backend@0.9.1-next.0 - - @backstage/plugin-permission-backend@0.5.14 - - @backstage/plugin-permission-common@0.7.2 - - @backstage/plugin-permission-node@0.7.2 - - @backstage/plugin-playlist-backend@0.2.3-next.0 - - @backstage/plugin-proxy-backend@0.2.33 - - @backstage/plugin-rollbar-backend@0.1.36 - - @backstage/plugin-search-backend-module-pg@0.5.1-next.0 - - @backstage/plugin-search-common@1.2.0 - - @backstage/plugin-tech-insights-backend@0.5.6-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23 - - @backstage/plugin-tech-insights-node@0.3.7 - - @backstage/plugin-techdocs-backend@1.5.1-next.0 - - @backstage/plugin-todo-backend@0.1.37-next.0 -## 0.2.78 +## 0.0.6 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.9.0 - - @backstage/plugin-azure-devops-backend@0.3.18 - - @backstage/plugin-kubernetes-backend@0.9.0 - @backstage/plugin-catalog-backend@1.6.0 - - @backstage/catalog-client@1.2.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.7 - - @backstage/plugin-search-backend@1.2.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.0 - - @backstage/plugin-search-backend-node@1.1.0 - - @backstage/plugin-playlist-backend@0.2.2 - - @backstage/plugin-search-backend-module-pg@0.5.0 - - @backstage/backend-common@0.17.0 - @backstage/plugin-app-backend@0.3.39 - - @backstage/plugin-catalog-node@1.3.0 - - @backstage/plugin-events-backend@0.2.0 - - @backstage/backend-tasks@0.4.0 - - @backstage/plugin-permission-backend@0.5.14 - - @backstage/plugin-permission-common@0.7.2 - - @backstage/plugin-permission-node@0.7.2 - - @backstage/plugin-kafka-backend@0.2.32 - - @backstage/plugin-jenkins-backend@0.1.29 - - @backstage/plugin-events-node@0.2.0 - - @backstage/integration@1.4.1 - - @backstage/plugin-auth-backend@0.17.2 - - @backstage/plugin-auth-node@0.2.8 - - @backstage/plugin-azure-sites-backend@0.1.1 - - @backstage/plugin-code-coverage-backend@0.2.5 - - @backstage/plugin-graphql-backend@0.1.29 - - @backstage/plugin-proxy-backend@0.2.33 - - @backstage/plugin-rollbar-backend@0.1.36 - - @backstage/plugin-techdocs-backend@1.5.0 - - @backstage/plugin-todo-backend@0.1.36 - - @backstage/plugin-explore-backend@0.0.1 - - @backstage/plugin-search-common@1.2.0 - - example-app@0.2.78 - - @backstage/plugin-badges-backend@0.1.33 - - @backstage/plugin-tech-insights-backend@0.5.5 - - @backstage/catalog-model@1.1.4 - - @backstage/config@1.0.5 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23 - - @backstage/plugin-tech-insights-node@0.3.7 + - @backstage/backend-defaults@0.1.4 -## 0.2.78-next.4 +## 0.0.6-next.3 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.6.0-next.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.3 - @backstage/plugin-scaffolder-backend@1.9.0-next.3 - - @backstage/backend-tasks@0.4.0-next.3 - - @backstage/plugin-permission-backend@0.5.14-next.3 - - @backstage/plugin-permission-common@0.7.2-next.2 - - @backstage/plugin-permission-node@0.7.2-next.3 - - @backstage/plugin-playlist-backend@0.2.2-next.4 - - @backstage/plugin-search-backend@1.2.0-next.3 - - @backstage/plugin-kubernetes-backend@0.8.1-next.4 - - @backstage/backend-common@0.17.0-next.3 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.0-next.3 - - @backstage/plugin-techdocs-backend@1.5.0-next.3 - - example-app@0.2.78-next.4 - - @backstage/catalog-client@1.2.0-next.1 - - @backstage/catalog-model@1.1.4-next.1 - - @backstage/config@1.0.5-next.1 - - @backstage/integration@1.4.1-next.1 + - @backstage/backend-defaults@0.1.4-next.3 - @backstage/plugin-app-backend@0.3.39-next.3 - - @backstage/plugin-auth-backend@0.17.2-next.3 - - @backstage/plugin-auth-node@0.2.8-next.3 - - @backstage/plugin-azure-devops-backend@0.3.18-next.3 - - @backstage/plugin-azure-sites-backend@0.1.1-next.3 - - @backstage/plugin-badges-backend@0.1.33-next.3 - - @backstage/plugin-catalog-node@1.3.0-next.3 - - @backstage/plugin-code-coverage-backend@0.2.5-next.3 - - @backstage/plugin-events-backend@0.2.0-next.3 - - @backstage/plugin-events-node@0.2.0-next.3 - - @backstage/plugin-explore-backend@0.0.1-next.2 - - @backstage/plugin-graphql-backend@0.1.29-next.3 - - @backstage/plugin-jenkins-backend@0.1.29-next.3 - - @backstage/plugin-kafka-backend@0.2.32-next.3 - - @backstage/plugin-proxy-backend@0.2.33-next.3 - - @backstage/plugin-rollbar-backend@0.1.36-next.3 - - @backstage/plugin-search-backend-module-pg@0.4.3-next.3 - - @backstage/plugin-search-backend-node@1.1.0-next.3 - - @backstage/plugin-search-common@1.2.0-next.3 - - @backstage/plugin-tech-insights-backend@0.5.5-next.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.3 - - @backstage/plugin-tech-insights-node@0.3.7-next.3 - - @backstage/plugin-todo-backend@0.1.36-next.3 -## 0.2.78-next.3 +## 0.0.6-next.2 ### Patch Changes - Updated dependencies - - example-app@0.2.78-next.3 - - @backstage/backend-common@0.17.0-next.2 - - @backstage/backend-tasks@0.4.0-next.2 - - @backstage/catalog-client@1.2.0-next.1 - - @backstage/catalog-model@1.1.4-next.1 - - @backstage/config@1.0.5-next.1 - - @backstage/integration@1.4.1-next.1 - - @backstage/plugin-app-backend@0.3.39-next.2 - - @backstage/plugin-auth-backend@0.17.2-next.2 - - @backstage/plugin-auth-node@0.2.8-next.2 - - @backstage/plugin-azure-devops-backend@0.3.18-next.2 - - @backstage/plugin-azure-sites-backend@0.1.1-next.2 - - @backstage/plugin-badges-backend@0.1.33-next.2 - @backstage/plugin-catalog-backend@1.6.0-next.2 - - @backstage/plugin-catalog-node@1.3.0-next.2 - - @backstage/plugin-code-coverage-backend@0.2.5-next.2 - - @backstage/plugin-events-backend@0.2.0-next.2 - - @backstage/plugin-events-node@0.2.0-next.2 - - @backstage/plugin-explore-backend@0.0.1-next.1 - - @backstage/plugin-graphql-backend@0.1.29-next.2 - - @backstage/plugin-jenkins-backend@0.1.29-next.2 - - @backstage/plugin-kafka-backend@0.2.32-next.2 - - @backstage/plugin-kubernetes-backend@0.8.1-next.3 - - @backstage/plugin-permission-backend@0.5.14-next.2 - - @backstage/plugin-permission-common@0.7.2-next.1 - - @backstage/plugin-permission-node@0.7.2-next.2 - - @backstage/plugin-playlist-backend@0.2.2-next.3 - - @backstage/plugin-proxy-backend@0.2.33-next.2 - - @backstage/plugin-rollbar-backend@0.1.36-next.2 - - @backstage/plugin-scaffolder-backend@1.9.0-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.2 - - @backstage/plugin-search-backend@1.2.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.0-next.2 - - @backstage/plugin-search-backend-module-pg@0.4.3-next.2 - - @backstage/plugin-search-backend-node@1.1.0-next.2 - - @backstage/plugin-search-common@1.2.0-next.2 - - @backstage/plugin-tech-insights-backend@0.5.5-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.2 - - @backstage/plugin-tech-insights-node@0.3.7-next.2 - - @backstage/plugin-techdocs-backend@1.4.2-next.2 - - @backstage/plugin-todo-backend@0.1.36-next.2 - -## 0.2.78-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-azure-devops-backend@0.3.18-next.2 - - @backstage/plugin-search-backend@1.2.0-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.1.0-next.2 - - @backstage/plugin-search-backend-node@1.1.0-next.2 - - @backstage/plugin-catalog-backend@1.6.0-next.2 - - @backstage/plugin-playlist-backend@0.2.2-next.2 - - @backstage/plugin-search-backend-module-pg@0.4.3-next.2 - @backstage/plugin-app-backend@0.3.39-next.2 - - @backstage/plugin-catalog-node@1.3.0-next.2 - - @backstage/plugin-events-backend@0.2.0-next.2 - @backstage/plugin-scaffolder-backend@1.9.0-next.2 - - @backstage/backend-common@0.17.0-next.2 - - @backstage/plugin-search-common@1.2.0-next.2 - - example-app@0.2.78-next.2 - - @backstage/plugin-techdocs-backend@1.4.2-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.2 - - @backstage/backend-tasks@0.4.0-next.2 - - @backstage/plugin-auth-backend@0.17.2-next.2 - - @backstage/plugin-auth-node@0.2.8-next.2 - - @backstage/plugin-azure-sites-backend@0.1.1-next.2 - - @backstage/plugin-badges-backend@0.1.33-next.2 - - @backstage/plugin-code-coverage-backend@0.2.5-next.2 - - @backstage/plugin-explore-backend@0.0.1-next.1 - - @backstage/plugin-graphql-backend@0.1.29-next.2 - - @backstage/plugin-jenkins-backend@0.1.29-next.2 - - @backstage/plugin-kafka-backend@0.2.32-next.2 - - @backstage/plugin-kubernetes-backend@0.8.1-next.2 - - @backstage/plugin-permission-backend@0.5.14-next.2 - - @backstage/plugin-permission-node@0.7.2-next.2 - - @backstage/plugin-proxy-backend@0.2.33-next.2 - - @backstage/plugin-rollbar-backend@0.1.36-next.2 - - @backstage/plugin-tech-insights-backend@0.5.5-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.2 - - @backstage/plugin-tech-insights-node@0.3.7-next.2 - - @backstage/plugin-todo-backend@0.1.36-next.2 - - @backstage/catalog-client@1.2.0-next.1 - - @backstage/catalog-model@1.1.4-next.1 - - @backstage/config@1.0.5-next.1 - - @backstage/integration@1.4.1-next.1 - - @backstage/plugin-events-node@0.2.0-next.2 - - @backstage/plugin-permission-common@0.7.2-next.1 + - @backstage/backend-defaults@0.1.4-next.2 -## 0.2.78-next.1 +## 0.0.6-next.1 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.17.0-next.1 - @backstage/plugin-catalog-backend@1.6.0-next.1 - - @backstage/plugin-kafka-backend@0.2.32-next.1 - - @backstage/backend-tasks@0.4.0-next.1 - - @backstage/plugin-search-backend-node@1.0.5-next.1 - - @backstage/plugin-jenkins-backend@0.1.29-next.1 - @backstage/plugin-scaffolder-backend@1.8.1-next.1 - - @backstage/plugin-explore-backend@0.0.1-next.0 - - @backstage/plugin-proxy-backend@0.2.33-next.1 - @backstage/plugin-app-backend@0.3.39-next.1 - - @backstage/plugin-auth-backend@0.17.2-next.1 - - @backstage/plugin-auth-node@0.2.8-next.1 - - @backstage/plugin-azure-devops-backend@0.3.18-next.1 - - @backstage/plugin-azure-sites-backend@0.1.1-next.1 - - @backstage/plugin-badges-backend@0.1.33-next.1 - - @backstage/plugin-catalog-node@1.2.2-next.1 - - @backstage/plugin-code-coverage-backend@0.2.5-next.1 - - @backstage/plugin-events-backend@0.2.0-next.1 - - @backstage/plugin-graphql-backend@0.1.29-next.1 - - @backstage/plugin-kubernetes-backend@0.8.1-next.1 - - @backstage/plugin-permission-backend@0.5.14-next.1 - - @backstage/plugin-permission-node@0.7.2-next.1 - - @backstage/plugin-playlist-backend@0.2.2-next.1 - - @backstage/plugin-rollbar-backend@0.1.36-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.1 - - @backstage/plugin-search-backend@1.1.2-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.5-next.1 - - @backstage/plugin-search-backend-module-pg@0.4.3-next.1 - - @backstage/plugin-tech-insights-backend@0.5.5-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.1 - - @backstage/plugin-tech-insights-node@0.3.7-next.1 - - @backstage/plugin-techdocs-backend@1.4.2-next.1 - - @backstage/plugin-todo-backend@0.1.36-next.1 - - example-app@0.2.78-next.1 - - @backstage/config@1.0.5-next.1 - - @backstage/integration@1.4.1-next.1 - - @backstage/catalog-client@1.2.0-next.1 - - @backstage/catalog-model@1.1.4-next.1 - - @backstage/plugin-events-node@0.2.0-next.1 - - @backstage/plugin-permission-common@0.7.2-next.1 - - @backstage/plugin-search-common@1.1.2-next.1 + - @backstage/backend-defaults@0.1.4-next.1 -## 0.2.78-next.0 +## 0.0.6-next.0 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.8.1-next.0 - - @backstage/catalog-client@1.2.0-next.0 - @backstage/plugin-catalog-backend@1.6.0-next.0 - - @backstage/plugin-events-backend@0.2.0-next.0 - - @backstage/plugin-search-backend-node@1.0.5-next.0 - - @backstage/plugin-events-node@0.2.0-next.0 - - @backstage/backend-common@0.16.1-next.0 - - @backstage/integration@1.4.1-next.0 - @backstage/plugin-app-backend@0.3.39-next.0 - - @backstage/plugin-auth-backend@0.17.2-next.0 - - @backstage/plugin-auth-node@0.2.8-next.0 - - @backstage/plugin-azure-devops-backend@0.3.18-next.0 - - @backstage/plugin-azure-sites-backend@0.1.1-next.0 - - @backstage/plugin-code-coverage-backend@0.2.5-next.0 - - @backstage/plugin-graphql-backend@0.1.29-next.0 - - @backstage/plugin-jenkins-backend@0.1.29-next.0 - - @backstage/plugin-permission-backend@0.5.14-next.0 - - @backstage/plugin-permission-common@0.7.2-next.0 - - @backstage/plugin-permission-node@0.7.2-next.0 - - @backstage/plugin-playlist-backend@0.2.2-next.0 - - @backstage/plugin-proxy-backend@0.2.33-next.0 - - @backstage/plugin-rollbar-backend@0.1.36-next.0 - - @backstage/plugin-techdocs-backend@1.4.2-next.0 - - @backstage/plugin-todo-backend@0.1.36-next.0 - - @backstage/plugin-kubernetes-backend@0.8.1-next.0 - - example-app@0.2.78-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.7-next.0 - - @backstage/plugin-badges-backend@0.1.33-next.0 - - @backstage/plugin-catalog-node@1.2.2-next.0 - - @backstage/plugin-tech-insights-backend@0.5.5-next.0 - - @backstage/backend-tasks@0.3.8-next.0 - - @backstage/catalog-model@1.1.4-next.0 - - @backstage/config@1.0.5-next.0 - - @backstage/plugin-kafka-backend@0.2.32-next.0 - - @backstage/plugin-search-backend@1.1.2-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.5-next.0 - - @backstage/plugin-search-backend-module-pg@0.4.3-next.0 - - @backstage/plugin-search-common@1.1.2-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.23-next.0 - - @backstage/plugin-tech-insights-node@0.3.7-next.0 + - @backstage/backend-defaults@0.1.4-next.0 -## 0.2.77 +## 0.0.5 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.16.0 - - @backstage/plugin-auth-backend@0.17.1 - @backstage/plugin-catalog-backend@1.5.1 - - @backstage/plugin-techdocs-backend@1.4.1 - @backstage/plugin-scaffolder-backend@1.8.0 - - @backstage/integration@1.4.0 - - @backstage/backend-tasks@0.3.7 - - @backstage/plugin-playlist-backend@0.2.1 - - @backstage/plugin-azure-devops-backend@0.3.17 - - @backstage/catalog-model@1.1.3 - - @backstage/plugin-auth-node@0.2.7 - - @backstage/plugin-permission-common@0.7.1 - - @backstage/plugin-code-coverage-backend@0.2.4 - - @backstage/plugin-events-backend@0.1.0 - - @backstage/plugin-events-node@0.1.0 - - @backstage/plugin-kubernetes-backend@0.8.0 - - @backstage/plugin-tech-insights-backend@0.5.4 - - @backstage/plugin-tech-insights-node@0.3.6 - - @backstage/plugin-azure-sites-backend@0.1.0 - - example-app@0.2.77 - @backstage/plugin-app-backend@0.3.38 - - @backstage/plugin-badges-backend@0.1.32 - - @backstage/plugin-catalog-node@1.2.1 - - @backstage/plugin-graphql-backend@0.1.28 - - @backstage/plugin-jenkins-backend@0.1.28 - - @backstage/plugin-kafka-backend@0.2.31 - - @backstage/plugin-permission-backend@0.5.13 - - @backstage/plugin-permission-node@0.7.1 - - @backstage/plugin-proxy-backend@0.2.32 - - @backstage/plugin-rollbar-backend@0.1.35 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.6 - - @backstage/plugin-search-backend@1.1.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.4 - - @backstage/plugin-search-backend-module-pg@0.4.2 - - @backstage/plugin-search-backend-node@1.0.4 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22 - - @backstage/plugin-todo-backend@0.1.35 - - @backstage/catalog-client@1.1.2 - - @backstage/config@1.0.4 - - @backstage/plugin-search-common@1.1.1 + - @backstage/backend-defaults@0.1.3 -## 0.2.77-next.2 +## 0.0.5-next.2 ### Patch Changes - Updated dependencies - - @backstage/plugin-auth-backend@0.17.1-next.1 - - @backstage/backend-common@0.16.0-next.1 - @backstage/plugin-scaffolder-backend@1.8.0-next.2 - - @backstage/plugin-code-coverage-backend@0.2.4-next.1 - - @backstage/plugin-kubernetes-backend@0.8.0-next.1 - - @backstage/plugin-tech-insights-backend@0.5.4-next.1 - - example-app@0.2.77-next.2 - - @backstage/backend-tasks@0.3.7-next.1 - @backstage/plugin-app-backend@0.3.38-next.1 - - @backstage/plugin-auth-node@0.2.7-next.1 - - @backstage/plugin-azure-devops-backend@0.3.17-next.2 - - @backstage/plugin-azure-sites-backend@0.1.0-next.1 - - @backstage/plugin-badges-backend@0.1.32-next.1 - @backstage/plugin-catalog-backend@1.5.1-next.1 - - @backstage/plugin-graphql-backend@0.1.28-next.1 - - @backstage/plugin-jenkins-backend@0.1.28-next.1 - - @backstage/plugin-kafka-backend@0.2.31-next.1 - - @backstage/plugin-permission-backend@0.5.13-next.1 - - @backstage/plugin-permission-node@0.7.1-next.1 - - @backstage/plugin-playlist-backend@0.2.1-next.2 - - @backstage/plugin-proxy-backend@0.2.32-next.1 - - @backstage/plugin-rollbar-backend@0.1.35-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.2 - - @backstage/plugin-search-backend@1.1.1-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.1 - - @backstage/plugin-search-backend-module-pg@0.4.2-next.1 - - @backstage/plugin-search-backend-node@1.0.4-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.1 - - @backstage/plugin-tech-insights-node@0.3.6-next.1 - - @backstage/plugin-techdocs-backend@1.4.1-next.1 - - @backstage/plugin-todo-backend@0.1.35-next.1 - - @backstage/catalog-client@1.1.2-next.0 - - @backstage/catalog-model@1.1.3-next.0 - - @backstage/config@1.0.4-next.0 - - @backstage/integration@1.4.0-next.0 - - @backstage/plugin-permission-common@0.7.1-next.0 - - @backstage/plugin-search-common@1.1.1-next.0 + - @backstage/backend-defaults@0.1.3-next.1 -## 0.2.77-next.1 +## 0.0.5-next.1 ### Patch Changes - Updated dependencies - @backstage/plugin-scaffolder-backend@1.8.0-next.1 - - @backstage/plugin-playlist-backend@0.2.1-next.1 - - @backstage/plugin-azure-devops-backend@0.3.17-next.1 - - example-app@0.2.77-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.1 -## 0.2.77-next.0 +## 0.0.5-next.0 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.16.0-next.0 - @backstage/plugin-catalog-backend@1.5.1-next.0 - - @backstage/plugin-techdocs-backend@1.4.1-next.0 - @backstage/plugin-scaffolder-backend@1.8.0-next.0 - - @backstage/integration@1.4.0-next.0 - - @backstage/plugin-auth-backend@0.17.1-next.0 - - @backstage/backend-tasks@0.3.7-next.0 - - @backstage/catalog-model@1.1.3-next.0 - - @backstage/plugin-auth-node@0.2.7-next.0 - - @backstage/plugin-permission-common@0.7.1-next.0 - - @backstage/plugin-tech-insights-backend@0.5.4-next.0 - - @backstage/plugin-tech-insights-node@0.3.6-next.0 - - @backstage/plugin-azure-sites-backend@0.1.0-next.0 - - @backstage/plugin-kubernetes-backend@0.8.0-next.0 - - example-app@0.2.77-next.0 - @backstage/plugin-app-backend@0.3.38-next.0 - - @backstage/plugin-azure-devops-backend@0.3.17-next.0 - - @backstage/plugin-badges-backend@0.1.32-next.0 - - @backstage/plugin-code-coverage-backend@0.2.4-next.0 - - @backstage/plugin-graphql-backend@0.1.28-next.0 - - @backstage/plugin-jenkins-backend@0.1.28-next.0 - - @backstage/plugin-kafka-backend@0.2.31-next.0 - - @backstage/plugin-permission-backend@0.5.13-next.0 - - @backstage/plugin-permission-node@0.7.1-next.0 - - @backstage/plugin-playlist-backend@0.2.1-next.0 - - @backstage/plugin-proxy-backend@0.2.32-next.0 - - @backstage/plugin-rollbar-backend@0.1.35-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 - - @backstage/plugin-search-backend@1.1.1-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 - - @backstage/plugin-search-backend-module-pg@0.4.2-next.0 - - @backstage/plugin-search-backend-node@1.0.4-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 - - @backstage/plugin-todo-backend@0.1.35-next.0 - - @backstage/catalog-client@1.1.2-next.0 - - @backstage/config@1.0.4-next.0 - - @backstage/plugin-search-common@1.1.1-next.0 + - @backstage/backend-defaults@0.1.3-next.0 -## 0.2.76 +## 0.0.4 ### Patch Changes - Updated dependencies - - @backstage/catalog-model@1.1.2 - - @backstage/backend-common@0.15.2 - @backstage/plugin-catalog-backend@1.5.0 - @backstage/plugin-scaffolder-backend@1.7.0 - - @backstage/plugin-auth-node@0.2.6 - - @backstage/backend-tasks@0.3.6 - - @backstage/plugin-permission-node@0.7.0 - - @backstage/plugin-auth-backend@0.17.0 - - @backstage/plugin-permission-common@0.7.0 - - @backstage/plugin-tech-insights-backend@0.5.3 - - @backstage/plugin-search-backend@1.1.0 - - @backstage/catalog-client@1.1.1 - - @backstage/plugin-playlist-backend@0.2.0 - - @backstage/plugin-jenkins-backend@0.1.27 + - @backstage/backend-defaults@0.1.2 - @backstage/plugin-app-backend@0.3.37 - - @backstage/plugin-badges-backend@0.1.31 - - @backstage/plugin-graphql-backend@0.1.27 - - @backstage/plugin-permission-backend@0.5.12 - - @backstage/plugin-rollbar-backend@0.1.34 - - @backstage/plugin-kubernetes-backend@0.7.3 - - @backstage/plugin-search-common@1.1.0 - - @backstage/plugin-search-backend-node@1.0.3 - - @backstage/plugin-search-backend-module-pg@0.4.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.3 - - @backstage/plugin-techdocs-backend@1.4.0 - - @backstage/plugin-tech-insights-node@0.3.5 - - example-app@0.2.76 - - @backstage/plugin-code-coverage-backend@0.2.3 - - @backstage/plugin-kafka-backend@0.2.30 - - @backstage/plugin-todo-backend@0.1.34 - - @backstage/plugin-azure-devops-backend@0.3.16 - - @backstage/plugin-proxy-backend@0.2.31 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.5 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 - - @backstage/config@1.0.3 - - @backstage/integration@1.3.2 -## 0.2.76-next.2 +## 0.0.4-next.2 ### Patch Changes - Updated dependencies - @backstage/plugin-catalog-backend@1.5.0-next.2 - - @backstage/backend-tasks@0.3.6-next.2 - - @backstage/backend-common@0.15.2-next.2 - - @backstage/plugin-permission-common@0.7.0-next.2 - - @backstage/plugin-permission-node@0.7.0-next.2 - @backstage/plugin-scaffolder-backend@1.7.0-next.2 - - @backstage/plugin-playlist-backend@0.2.0-next.2 - - @backstage/plugin-badges-backend@0.1.31-next.2 - - @backstage/plugin-graphql-backend@0.1.27-next.2 - - @backstage/plugin-permission-backend@0.5.12-next.2 - - @backstage/plugin-rollbar-backend@0.1.34-next.2 - - @backstage/plugin-search-backend@1.1.0-next.2 - - @backstage/plugin-tech-insights-backend@0.5.3-next.2 - - @backstage/plugin-techdocs-backend@1.4.0-next.2 - - example-app@0.2.76-next.2 - - @backstage/plugin-search-backend-node@1.0.3-next.2 - - @backstage/plugin-tech-insights-node@0.3.5-next.2 - @backstage/plugin-app-backend@0.3.37-next.2 - - @backstage/plugin-auth-backend@0.17.0-next.2 - - @backstage/plugin-auth-node@0.2.6-next.2 - - @backstage/plugin-azure-devops-backend@0.3.16-next.2 - - @backstage/plugin-code-coverage-backend@0.2.3-next.2 - - @backstage/plugin-jenkins-backend@0.1.27-next.2 - - @backstage/plugin-kafka-backend@0.2.30-next.2 - - @backstage/plugin-kubernetes-backend@0.7.3-next.2 - - @backstage/plugin-proxy-backend@0.2.31-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 - - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 - - @backstage/plugin-todo-backend@0.1.34-next.2 - - @backstage/plugin-search-common@1.1.0-next.2 - - @backstage/catalog-client@1.1.1-next.2 - - @backstage/catalog-model@1.1.2-next.2 - - @backstage/config@1.0.3-next.2 - - @backstage/integration@1.3.2-next.2 + - @backstage/backend-defaults@0.1.2-next.2 -## 0.2.76-next.1 +## 0.0.4-next.1 ### Patch Changes - Updated dependencies - - @backstage/plugin-auth-backend@0.17.0-next.1 - - @backstage/plugin-search-backend@1.1.0-next.1 - - @backstage/catalog-client@1.1.1-next.1 - - @backstage/backend-common@0.15.2-next.1 - @backstage/plugin-scaffolder-backend@1.7.0-next.1 - - @backstage/plugin-search-common@1.1.0-next.1 - - @backstage/plugin-search-backend-node@1.0.3-next.1 - - @backstage/plugin-search-backend-module-pg@0.4.1-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.1 - - @backstage/plugin-kubernetes-backend@0.7.3-next.1 - - @backstage/plugin-tech-insights-backend@0.5.3-next.1 - - example-app@0.2.76-next.1 - - @backstage/backend-tasks@0.3.6-next.1 - - @backstage/catalog-model@1.1.2-next.1 - - @backstage/config@1.0.3-next.1 - - @backstage/integration@1.3.2-next.1 + - @backstage/backend-defaults@0.1.2-next.1 - @backstage/plugin-app-backend@0.3.37-next.1 - - @backstage/plugin-auth-node@0.2.6-next.1 - - @backstage/plugin-azure-devops-backend@0.3.16-next.1 - - @backstage/plugin-badges-backend@0.1.31-next.1 - @backstage/plugin-catalog-backend@1.4.1-next.1 - - @backstage/plugin-code-coverage-backend@0.2.3-next.1 - - @backstage/plugin-graphql-backend@0.1.27-next.1 - - @backstage/plugin-jenkins-backend@0.1.27-next.1 - - @backstage/plugin-kafka-backend@0.2.30-next.1 - - @backstage/plugin-permission-backend@0.5.12-next.1 - - @backstage/plugin-permission-common@0.6.5-next.1 - - @backstage/plugin-permission-node@0.6.6-next.1 - - @backstage/plugin-playlist-backend@0.1.1-next.1 - - @backstage/plugin-proxy-backend@0.2.31-next.1 - - @backstage/plugin-rollbar-backend@0.1.34-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.1 - - @backstage/plugin-tech-insights-node@0.3.5-next.1 - - @backstage/plugin-techdocs-backend@1.3.1-next.1 - - @backstage/plugin-todo-backend@0.1.34-next.1 -## 0.2.76-next.0 +## 0.0.4-next.0 ### Patch Changes - Updated dependencies - - @backstage/catalog-model@1.1.2-next.0 - @backstage/plugin-scaffolder-backend@1.7.0-next.0 - - @backstage/plugin-auth-backend@0.17.0-next.0 + - @backstage/backend-defaults@0.1.2-next.0 - @backstage/plugin-catalog-backend@1.4.1-next.0 - - @backstage/plugin-jenkins-backend@0.1.27-next.0 - @backstage/plugin-app-backend@0.3.37-next.0 - - @backstage/plugin-tech-insights-node@0.3.5-next.0 - - example-app@0.2.76-next.0 - - @backstage/catalog-client@1.1.1-next.0 - - @backstage/plugin-badges-backend@0.1.31-next.0 - - @backstage/plugin-code-coverage-backend@0.2.3-next.0 - - @backstage/plugin-kafka-backend@0.2.30-next.0 - - @backstage/plugin-kubernetes-backend@0.7.3-next.0 - - @backstage/plugin-playlist-backend@0.1.1-next.0 - - @backstage/plugin-tech-insights-backend@0.5.3-next.0 - - @backstage/plugin-techdocs-backend@1.3.1-next.0 - - @backstage/plugin-todo-backend@0.1.34-next.0 - - @backstage/backend-common@0.15.2-next.0 - - @backstage/backend-tasks@0.3.6-next.0 - - @backstage/plugin-auth-node@0.2.6-next.0 - - @backstage/plugin-permission-node@0.6.6-next.0 - - @backstage/plugin-rollbar-backend@0.1.34-next.0 - - @backstage/plugin-search-backend-module-pg@0.4.1-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.0 - - @backstage/config@1.0.3-next.0 - - @backstage/integration@1.3.2-next.0 - - @backstage/plugin-azure-devops-backend@0.3.16-next.0 - - @backstage/plugin-graphql-backend@0.1.27-next.0 - - @backstage/plugin-permission-backend@0.5.12-next.0 - - @backstage/plugin-permission-common@0.6.5-next.0 - - @backstage/plugin-proxy-backend@0.2.31-next.0 - - @backstage/plugin-search-backend@1.0.3-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.0 - - @backstage/plugin-search-backend-node@1.0.3-next.0 - - @backstage/plugin-search-common@1.0.2-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.0 -## 0.2.75 +## 0.0.3 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.15.1 - @backstage/plugin-scaffolder-backend@1.6.0 - - @backstage/plugin-auth-node@0.2.5 - - @backstage/plugin-permission-node@0.6.5 - - @backstage/plugin-kubernetes-backend@0.7.2 - - @backstage/plugin-kafka-backend@0.2.29 - - @backstage/plugin-proxy-backend@0.2.30 - - @backstage/plugin-auth-backend@0.16.0 - - @backstage/integration@1.3.1 - @backstage/plugin-catalog-backend@1.4.0 - - @backstage/plugin-azure-devops-backend@0.3.15 - - @backstage/plugin-search-backend-node@1.0.2 - - @backstage/plugin-tech-insights-node@0.3.4 - - @backstage/backend-tasks@0.3.5 - - @backstage/plugin-techdocs-backend@1.3.0 - - @backstage/catalog-client@1.1.0 - - @backstage/catalog-model@1.1.1 - - @backstage/config@1.0.2 - - @backstage/plugin-permission-common@0.6.4 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.4 - - @backstage/plugin-search-backend-module-pg@0.4.0 - - @backstage/plugin-jenkins-backend@0.1.26 - - @backstage/plugin-playlist-backend@0.1.0 - - @backstage/plugin-app-backend@0.3.36 - - @backstage/plugin-graphql-backend@0.1.26 - - @backstage/plugin-rollbar-backend@0.1.33 - - @backstage/plugin-code-coverage-backend@0.2.2 - - @backstage/plugin-permission-backend@0.5.11 - - @backstage/plugin-todo-backend@0.1.33 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.2 - - @backstage/plugin-tech-insights-backend@0.5.2 - - @backstage/plugin-badges-backend@0.1.30 - - example-app@0.2.75 - - @backstage/plugin-search-backend@1.0.2 - - @backstage/plugin-search-common@1.0.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.20 + - @backstage/backend-defaults@0.1.1 -## 0.2.75-next.3 +## 0.0.3-next.1 ### Patch Changes - Updated dependencies - - @backstage/catalog-client@1.1.0-next.2 - - @backstage/catalog-model@1.1.1-next.0 - - @backstage/config@1.0.2-next.0 - - @backstage/integration@1.3.1-next.2 - - @backstage/plugin-permission-common@0.6.4-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.4-next.1 - - @backstage/plugin-catalog-backend@1.4.0-next.3 - - @backstage/plugin-auth-backend@0.16.0-next.3 - - @backstage/backend-common@0.15.1-next.3 - - @backstage/plugin-scaffolder-backend@1.6.0-next.3 - - @backstage/plugin-badges-backend@0.1.30-next.1 - - @backstage/plugin-code-coverage-backend@0.2.2-next.2 - - @backstage/plugin-jenkins-backend@0.1.26-next.3 - - @backstage/plugin-kubernetes-backend@0.7.2-next.3 - - @backstage/plugin-tech-insights-backend@0.5.2-next.2 - - @backstage/plugin-techdocs-backend@1.3.0-next.2 - - @backstage/plugin-todo-backend@0.1.33-next.2 - - example-app@0.2.75-next.3 - - @backstage/plugin-kafka-backend@0.2.29-next.1 - - @backstage/backend-tasks@0.3.5-next.1 - - @backstage/plugin-app-backend@0.3.36-next.3 - - @backstage/plugin-auth-node@0.2.5-next.3 - - @backstage/plugin-azure-devops-backend@0.3.15-next.2 - - @backstage/plugin-graphql-backend@0.1.26-next.3 - - @backstage/plugin-permission-backend@0.5.11-next.2 - - @backstage/plugin-permission-node@0.6.5-next.3 - - @backstage/plugin-proxy-backend@0.2.30-next.2 - - @backstage/plugin-rollbar-backend@0.1.33-next.3 - - @backstage/plugin-search-backend@1.0.2-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.2-next.2 - - @backstage/plugin-search-backend-module-pg@0.4.0-next.2 - - @backstage/plugin-search-backend-node@1.0.2-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.20-next.1 - - @backstage/plugin-tech-insights-node@0.3.4-next.1 - -## 0.2.75-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.7.2-next.2 - - @backstage/backend-common@0.15.1-next.2 - - @backstage/integration@1.3.1-next.1 - - @backstage/plugin-catalog-backend@1.4.0-next.2 - - @backstage/plugin-scaffolder-backend@1.6.0-next.2 - - @backstage/plugin-auth-node@0.2.5-next.2 - - @backstage/plugin-techdocs-backend@1.3.0-next.1 - - @backstage/plugin-jenkins-backend@0.1.26-next.2 - - @backstage/catalog-client@1.0.5-next.1 - - @backstage/plugin-app-backend@0.3.36-next.2 - - @backstage/plugin-auth-backend@0.16.0-next.2 - - @backstage/plugin-azure-devops-backend@0.3.15-next.1 - - @backstage/plugin-code-coverage-backend@0.2.2-next.1 - - @backstage/plugin-graphql-backend@0.1.26-next.2 - - @backstage/plugin-permission-backend@0.5.11-next.1 - - @backstage/plugin-permission-common@0.6.4-next.1 - - @backstage/plugin-permission-node@0.6.5-next.2 - - @backstage/plugin-proxy-backend@0.2.30-next.1 - - @backstage/plugin-rollbar-backend@0.1.33-next.2 - - @backstage/plugin-todo-backend@0.1.33-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.2-next.1 - - example-app@0.2.75-next.2 - -## 0.2.75-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-node@0.2.5-next.1 - - @backstage/plugin-permission-node@0.6.5-next.1 - - @backstage/backend-common@0.15.1-next.1 - @backstage/plugin-catalog-backend@1.4.0-next.1 - - @backstage/plugin-auth-backend@0.16.0-next.1 - @backstage/plugin-scaffolder-backend@1.6.0-next.1 - - @backstage/plugin-search-backend-node@1.0.2-next.1 - - @backstage/plugin-app-backend@0.3.36-next.1 - - @backstage/plugin-graphql-backend@0.1.26-next.1 - - @backstage/plugin-jenkins-backend@0.1.26-next.1 - - @backstage/plugin-rollbar-backend@0.1.33-next.1 - - @backstage/plugin-search-backend-module-pg@0.4.0-next.1 - - @backstage/plugin-kubernetes-backend@0.7.2-next.1 - - @backstage/plugin-tech-insights-backend@0.5.2-next.1 - - example-app@0.2.75-next.1 -## 0.2.75-next.0 +## 0.0.3-next.0 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.15.1-next.0 - @backstage/plugin-scaffolder-backend@1.6.0-next.0 - - @backstage/plugin-kafka-backend@0.2.29-next.0 - - @backstage/plugin-proxy-backend@0.2.30-next.0 - - @backstage/plugin-azure-devops-backend@0.3.15-next.0 - - @backstage/plugin-search-backend-node@1.0.2-next.0 - - @backstage/plugin-tech-insights-node@0.3.4-next.0 - - @backstage/backend-tasks@0.3.5-next.0 - @backstage/plugin-catalog-backend@1.3.2-next.0 - - @backstage/plugin-search-backend-module-pg@0.4.0-next.0 - - @backstage/catalog-client@1.0.5-next.0 - - @backstage/integration@1.3.1-next.0 - - @backstage/plugin-app-backend@0.3.36-next.0 - - @backstage/plugin-auth-backend@0.15.2-next.0 - - @backstage/plugin-auth-node@0.2.5-next.0 - - @backstage/plugin-code-coverage-backend@0.2.2-next.0 - - @backstage/plugin-graphql-backend@0.1.26-next.0 - - @backstage/plugin-jenkins-backend@0.1.26-next.0 - - @backstage/plugin-permission-backend@0.5.11-next.0 - - @backstage/plugin-permission-common@0.6.4-next.0 - - @backstage/plugin-permission-node@0.6.5-next.0 - - @backstage/plugin-rollbar-backend@0.1.33-next.0 - - @backstage/plugin-techdocs-backend@1.2.2-next.0 - - @backstage/plugin-todo-backend@0.1.33-next.0 - - @backstage/plugin-tech-insights-backend@0.5.2-next.0 - - @backstage/plugin-badges-backend@0.1.30-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.2-next.0 - - @backstage/plugin-kubernetes-backend@0.7.2-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.4-next.0 - - @backstage/plugin-search-backend@1.0.2-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.20-next.0 - - example-app@0.2.75-next.0 - - @backstage/plugin-search-common@1.0.1-next.0 + - @backstage/backend-defaults@0.1.1-next.0 -## 0.2.74 +## 0.0.2 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.15.0 - - @backstage/plugin-kubernetes-backend@0.7.1 - - @backstage/integration@1.3.0 - @backstage/plugin-scaffolder-backend@1.5.0 - - @backstage/plugin-auth-backend@0.15.1 - - @backstage/plugin-graphql-backend@0.1.25 - - @backstage/backend-tasks@0.3.4 - - @backstage/plugin-tech-insights-node@0.3.3 + - @backstage/backend-defaults@0.1.0 - @backstage/plugin-catalog-backend@1.3.1 - - example-app@0.2.74 - - @backstage/plugin-app-backend@0.3.35 - - @backstage/plugin-auth-node@0.2.4 - - @backstage/plugin-azure-devops-backend@0.3.14 - - @backstage/plugin-badges-backend@0.1.29 - - @backstage/plugin-code-coverage-backend@0.2.1 - - @backstage/plugin-jenkins-backend@0.1.25 - - @backstage/plugin-kafka-backend@0.2.28 - - @backstage/plugin-permission-backend@0.5.10 - - @backstage/plugin-permission-node@0.6.4 - - @backstage/plugin-proxy-backend@0.2.29 - - @backstage/plugin-rollbar-backend@0.1.32 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.3 - - @backstage/plugin-search-backend@1.0.1 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.1 - - @backstage/plugin-search-backend-module-pg@0.3.6 - - @backstage/plugin-search-backend-node@1.0.1 - - @backstage/plugin-tech-insights-backend@0.5.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.19 - - @backstage/plugin-techdocs-backend@1.2.1 - - @backstage/plugin-todo-backend@0.1.32 -## 0.2.74-next.0 +## 0.0.2-next.0 ### Patch Changes - Updated dependencies - - @backstage/backend-common@0.15.0-next.0 - - @backstage/integration@1.3.0-next.0 - @backstage/plugin-scaffolder-backend@1.5.0-next.0 - - @backstage/backend-tasks@0.3.4-next.0 - - @backstage/plugin-kubernetes-backend@0.7.1-next.0 - - @backstage/plugin-tech-insights-node@0.3.3-next.0 - - @backstage/plugin-app-backend@0.3.35-next.0 - - @backstage/plugin-auth-backend@0.15.1-next.0 - - @backstage/plugin-auth-node@0.2.4-next.0 - - @backstage/plugin-azure-devops-backend@0.3.14-next.0 - - @backstage/plugin-badges-backend@0.1.29-next.0 + - @backstage/backend-app-api@0.1.1-next.0 - @backstage/plugin-catalog-backend@1.3.1-next.0 - - @backstage/plugin-code-coverage-backend@0.2.1-next.0 - - @backstage/plugin-graphql-backend@0.1.25-next.0 - - @backstage/plugin-jenkins-backend@0.1.25-next.0 - - @backstage/plugin-kafka-backend@0.2.28-next.0 - - @backstage/plugin-permission-backend@0.5.10-next.0 - - @backstage/plugin-permission-node@0.6.4-next.0 - - @backstage/plugin-proxy-backend@0.2.29-next.0 - - @backstage/plugin-rollbar-backend@0.1.32-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.3-next.0 - - @backstage/plugin-search-backend@1.0.1-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.1-next.0 - - @backstage/plugin-search-backend-module-pg@0.3.6-next.0 - - @backstage/plugin-search-backend-node@1.0.1-next.0 - - @backstage/plugin-tech-insights-backend@0.5.1-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.19-next.0 - - @backstage/plugin-techdocs-backend@1.2.1-next.0 - - @backstage/plugin-todo-backend@0.1.32-next.0 - - example-app@0.2.74-next.0 -## 0.2.73 +## 0.0.1 ### Patch Changes - Updated dependencies - - @backstage/plugin-code-coverage-backend@0.2.0 - @backstage/plugin-catalog-backend@1.3.0 - - @backstage/plugin-tech-insights-backend@0.5.0 - - @backstage/backend-common@0.14.1 - - @backstage/catalog-model@1.1.0 - - @backstage/plugin-kubernetes-backend@0.7.0 - - @backstage/plugin-search-backend@1.0.0 - - @backstage/plugin-search-backend-node@1.0.0 - - @backstage/plugin-search-common@1.0.0 - - @backstage/plugin-search-backend-module-elasticsearch@1.0.0 - @backstage/plugin-scaffolder-backend@1.4.0 - - @backstage/plugin-auth-backend@0.15.0 - - @backstage/plugin-jenkins-backend@0.1.24 - - @backstage/plugin-proxy-backend@0.2.28 - - @backstage/plugin-search-backend-module-pg@0.3.5 - - @backstage/integration@1.2.2 - - @backstage/catalog-client@1.0.4 - - @backstage/plugin-app-backend@0.3.34 - - @backstage/plugin-auth-node@0.2.3 - - @backstage/plugin-azure-devops-backend@0.3.13 - - @backstage/plugin-graphql-backend@0.1.24 - - @backstage/plugin-permission-backend@0.5.9 - - @backstage/plugin-permission-common@0.6.3 - - @backstage/plugin-permission-node@0.6.3 - - @backstage/plugin-rollbar-backend@0.1.31 - - @backstage/plugin-techdocs-backend@1.2.0 - - @backstage/plugin-todo-backend@0.1.31 - - @backstage/backend-tasks@0.3.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18 - - @backstage/plugin-tech-insights-node@0.3.2 - - @backstage/plugin-kafka-backend@0.2.27 - - @backstage/plugin-badges-backend@0.1.28 - - example-app@0.2.73 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.2 + - @backstage/backend-app-api@0.1.0 -## 0.2.73-next.3 +## 0.0.1-next.0 ### Patch Changes - Updated dependencies - - @backstage/plugin-code-coverage-backend@0.2.0-next.3 - @backstage/plugin-catalog-backend@1.3.0-next.3 - - @backstage/plugin-kubernetes-backend@0.7.0-next.3 - - @backstage/plugin-proxy-backend@0.2.28-next.1 - - @backstage/backend-common@0.14.1-next.3 - @backstage/plugin-scaffolder-backend@1.4.0-next.3 - - @backstage/catalog-client@1.0.4-next.2 - - @backstage/integration@1.2.2-next.3 - - @backstage/plugin-app-backend@0.3.34-next.3 - - @backstage/plugin-auth-backend@0.15.0-next.3 - - @backstage/plugin-auth-node@0.2.3-next.2 - - @backstage/plugin-azure-devops-backend@0.3.13-next.1 - - @backstage/plugin-graphql-backend@0.1.24-next.1 - - @backstage/plugin-jenkins-backend@0.1.24-next.3 - - @backstage/plugin-permission-backend@0.5.9-next.2 - - @backstage/plugin-permission-common@0.6.3-next.1 - - @backstage/plugin-permission-node@0.6.3-next.2 - - @backstage/plugin-rollbar-backend@0.1.31-next.1 - - @backstage/plugin-techdocs-backend@1.2.0-next.3 - - @backstage/plugin-todo-backend@0.1.31-next.2 - - @backstage/backend-tasks@0.3.3-next.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18-next.2 - - @backstage/plugin-tech-insights-backend@0.5.0-next.3 - - @backstage/plugin-tech-insights-node@0.3.2-next.1 - - @backstage/catalog-model@1.1.0-next.3 - - @backstage/plugin-search-backend-module-elasticsearch@0.2.0-next.2 - - @backstage/plugin-search-backend-node@0.6.3-next.2 - - @backstage/plugin-search-backend@0.5.4-next.2 - - example-app@0.2.73-next.3 - -## 0.2.73-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.7.0-next.2 - - @backstage/plugin-tech-insights-backend@0.5.0-next.2 - - @backstage/plugin-jenkins-backend@0.1.24-next.2 - - @backstage/plugin-search-backend-module-pg@0.3.5-next.2 - - @backstage/plugin-scaffolder-backend@1.4.0-next.2 - - @backstage/plugin-auth-backend@0.15.0-next.2 - - @backstage/catalog-model@1.1.0-next.2 - - @backstage/plugin-kafka-backend@0.2.27-next.2 - - @backstage/backend-common@0.14.1-next.2 - - @backstage/backend-tasks@0.3.3-next.2 - - @backstage/plugin-app-backend@0.3.34-next.2 - - @backstage/plugin-catalog-backend@1.2.1-next.2 - - @backstage/plugin-code-coverage-backend@0.1.32-next.2 - - @backstage/plugin-techdocs-backend@1.2.0-next.2 - - @backstage/plugin-badges-backend@0.1.28-next.2 - - @backstage/integration@1.2.2-next.2 - - example-app@0.2.73-next.2 - -## 0.2.73-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.4.0-next.1 - - @backstage/plugin-auth-backend@0.15.0-next.1 - - @backstage/catalog-model@1.1.0-next.1 - - @backstage/backend-common@0.14.1-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@0.2.0-next.1 - - @backstage/plugin-catalog-backend@1.2.1-next.1 - - @backstage/plugin-techdocs-backend@1.2.0-next.1 - - example-app@0.2.73-next.1 - - @backstage/backend-tasks@0.3.3-next.1 - - @backstage/catalog-client@1.0.4-next.1 - - @backstage/integration@1.2.2-next.1 - - @backstage/plugin-app-backend@0.3.34-next.1 - - @backstage/plugin-auth-node@0.2.3-next.1 - - @backstage/plugin-badges-backend@0.1.28-next.1 - - @backstage/plugin-code-coverage-backend@0.1.32-next.1 - - @backstage/plugin-jenkins-backend@0.1.24-next.1 - - @backstage/plugin-kafka-backend@0.2.27-next.1 - - @backstage/plugin-kubernetes-backend@0.7.0-next.1 - - @backstage/plugin-permission-backend@0.5.9-next.1 - - @backstage/plugin-permission-common@0.6.3-next.0 - - @backstage/plugin-permission-node@0.6.3-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.2-next.1 - - @backstage/plugin-search-backend@0.5.4-next.1 - - @backstage/plugin-search-backend-module-pg@0.3.5-next.1 - - @backstage/plugin-search-backend-node@0.6.3-next.1 - - @backstage/plugin-tech-insights-backend@0.4.2-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18-next.1 - - @backstage/plugin-todo-backend@0.1.31-next.1 - -## 0.2.73-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-tech-insights-backend@0.4.2-next.0 - - @backstage/backend-common@0.14.1-next.0 - - @backstage/catalog-model@1.1.0-next.0 - - @backstage/plugin-scaffolder-backend@1.4.0-next.0 - - @backstage/plugin-auth-backend@0.14.2-next.0 - - @backstage/plugin-kubernetes-backend@0.7.0-next.0 - - @backstage/integration@1.2.2-next.0 - - @backstage/plugin-azure-devops-backend@0.3.13-next.0 - - example-app@0.2.73-next.0 - - @backstage/backend-tasks@0.3.3-next.0 - - @backstage/plugin-app-backend@0.3.34-next.0 - - @backstage/plugin-auth-node@0.2.3-next.0 - - @backstage/plugin-badges-backend@0.1.28-next.0 - - @backstage/plugin-catalog-backend@1.2.1-next.0 - - @backstage/plugin-code-coverage-backend@0.1.32-next.0 - - @backstage/plugin-graphql-backend@0.1.24-next.0 - - @backstage/plugin-jenkins-backend@0.1.24-next.0 - - @backstage/plugin-kafka-backend@0.2.27-next.0 - - @backstage/plugin-permission-backend@0.5.9-next.0 - - @backstage/plugin-permission-node@0.6.3-next.0 - - @backstage/plugin-proxy-backend@0.2.28-next.0 - - @backstage/plugin-rollbar-backend@0.1.31-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.2-next.0 - - @backstage/plugin-search-backend@0.5.4-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.6-next.0 - - @backstage/plugin-search-backend-module-pg@0.3.5-next.0 - - @backstage/plugin-search-backend-node@0.6.3-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.18-next.0 - - @backstage/plugin-tech-insights-node@0.3.2-next.0 - - @backstage/plugin-techdocs-backend@1.1.3-next.0 - - @backstage/plugin-todo-backend@0.1.31-next.0 - - @backstage/catalog-client@1.0.4-next.0 - -## 0.2.72 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-tech-insights-backend@0.4.1 - - @backstage/plugin-catalog-backend@1.2.0 - - @backstage/plugin-auth-backend@0.14.1 - - @backstage/plugin-scaffolder-backend@1.3.0 - - @backstage/backend-tasks@0.3.2 - - @backstage/plugin-permission-node@0.6.2 - - @backstage/plugin-kubernetes-backend@0.6.0 - - @backstage/backend-common@0.14.0 - - @backstage/plugin-search-backend@0.5.3 - - @backstage/plugin-auth-node@0.2.2 - - @backstage/integration@1.2.1 - - @backstage/plugin-jenkins-backend@0.1.23 - - @backstage/plugin-search-backend-node@0.6.2 - - @backstage/catalog-client@1.0.3 - - @backstage/plugin-app-backend@0.3.33 - - @backstage/plugin-azure-devops-backend@0.3.12 - - @backstage/plugin-code-coverage-backend@0.1.31 - - @backstage/plugin-graphql-backend@0.1.23 - - @backstage/plugin-permission-backend@0.5.8 - - @backstage/plugin-permission-common@0.6.2 - - @backstage/plugin-rollbar-backend@0.1.30 - - @backstage/plugin-techdocs-backend@1.1.2 - - @backstage/plugin-todo-backend@0.1.30 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.5 - - @backstage/plugin-search-backend-module-pg@0.3.4 - - @backstage/catalog-model@1.0.3 - - @backstage/plugin-tech-insights-node@0.3.1 - - example-app@0.2.72 - - @backstage/plugin-badges-backend@0.1.27 - - @backstage/plugin-kafka-backend@0.2.26 - - @backstage/plugin-proxy-backend@0.2.27 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17 - -## 0.2.72-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@1.3.0-next.2 - - @backstage/backend-common@0.14.0-next.2 - - @backstage/plugin-search-backend@0.5.3-next.2 - - @backstage/plugin-auth-backend@0.14.1-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.2 - - @backstage/integration@1.2.1-next.2 - - @backstage/plugin-techdocs-backend@1.1.2-next.2 - - @backstage/plugin-search-backend-node@0.6.2-next.2 - - example-app@0.2.72-next.2 - - @backstage/backend-tasks@0.3.2-next.2 - - @backstage/plugin-app-backend@0.3.33-next.2 - - @backstage/plugin-auth-node@0.2.2-next.2 - - @backstage/plugin-azure-devops-backend@0.3.12-next.2 - - @backstage/plugin-badges-backend@0.1.27-next.2 - - @backstage/plugin-catalog-backend@1.2.0-next.2 - - @backstage/plugin-code-coverage-backend@0.1.31-next.2 - - @backstage/plugin-graphql-backend@0.1.23-next.2 - - @backstage/plugin-jenkins-backend@0.1.23-next.2 - - @backstage/plugin-kafka-backend@0.2.26-next.2 - - @backstage/plugin-kubernetes-backend@0.6.0-next.2 - - @backstage/plugin-permission-backend@0.5.8-next.2 - - @backstage/plugin-permission-node@0.6.2-next.2 - - @backstage/plugin-proxy-backend@0.2.27-next.1 - - @backstage/plugin-rollbar-backend@0.1.30-next.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.1-next.1 - - @backstage/plugin-search-backend-module-pg@0.3.4-next.2 - - @backstage/plugin-tech-insights-backend@0.4.1-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17-next.1 - - @backstage/plugin-tech-insights-node@0.3.1-next.1 - - @backstage/plugin-todo-backend@0.1.30-next.2 - -## 0.2.72-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-tech-insights-backend@0.4.1-next.1 - - @backstage/plugin-auth-backend@0.14.1-next.1 - - @backstage/plugin-jenkins-backend@0.1.23-next.1 - - @backstage/backend-tasks@0.3.2-next.1 - - @backstage/backend-common@0.13.6-next.1 - - @backstage/catalog-client@1.0.3-next.0 - - @backstage/integration@1.2.1-next.1 - - @backstage/plugin-app-backend@0.3.33-next.1 - - @backstage/plugin-auth-node@0.2.2-next.1 - - @backstage/plugin-azure-devops-backend@0.3.12-next.1 - - @backstage/plugin-catalog-backend@1.2.0-next.1 - - @backstage/plugin-code-coverage-backend@0.1.31-next.1 - - @backstage/plugin-graphql-backend@0.1.23-next.1 - - @backstage/plugin-permission-backend@0.5.8-next.1 - - @backstage/plugin-permission-common@0.6.2-next.0 - - @backstage/plugin-permission-node@0.6.2-next.1 - - @backstage/plugin-rollbar-backend@0.1.30-next.1 - - @backstage/plugin-scaffolder-backend@1.3.0-next.1 - - @backstage/plugin-techdocs-backend@1.1.2-next.1 - - @backstage/plugin-todo-backend@0.1.30-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.1 - - @backstage/plugin-search-backend-node@0.6.2-next.1 - - @backstage/catalog-model@1.0.3-next.0 - - @backstage/plugin-badges-backend@0.1.27-next.1 - - example-app@0.2.72-next.1 - - @backstage/plugin-search-backend@0.5.3-next.1 - - @backstage/plugin-kafka-backend@0.2.26-next.1 - - @backstage/plugin-kubernetes-backend@0.6.0-next.1 - - @backstage/plugin-search-backend-module-pg@0.3.4-next.1 - -## 0.2.72-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-tasks@0.3.2-next.0 - - @backstage/plugin-scaffolder-backend@1.3.0-next.0 - - @backstage/plugin-kubernetes-backend@0.6.0-next.0 - - @backstage/backend-common@0.13.6-next.0 - - @backstage/plugin-auth-backend@0.14.1-next.0 - - @backstage/integration@1.2.1-next.0 - - @backstage/plugin-search-backend-node@0.6.2-next.0 - - @backstage/plugin-catalog-backend@1.2.0-next.0 - - @backstage/plugin-auth-node@0.2.2-next.0 - - @backstage/plugin-techdocs-backend@1.1.2-next.0 - - example-app@0.2.72-next.0 - - @backstage/plugin-app-backend@0.3.33-next.0 - - @backstage/plugin-azure-devops-backend@0.3.12-next.0 - - @backstage/plugin-badges-backend@0.1.27-next.0 - - @backstage/plugin-code-coverage-backend@0.1.31-next.0 - - @backstage/plugin-graphql-backend@0.1.23-next.0 - - @backstage/plugin-jenkins-backend@0.1.23-next.0 - - @backstage/plugin-kafka-backend@0.2.26-next.0 - - @backstage/plugin-permission-backend@0.5.8-next.0 - - @backstage/plugin-permission-node@0.6.2-next.0 - - @backstage/plugin-proxy-backend@0.2.27-next.0 - - @backstage/plugin-rollbar-backend@0.1.30-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.1-next.0 - - @backstage/plugin-search-backend@0.5.3-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.0 - - @backstage/plugin-search-backend-module-pg@0.3.4-next.0 - - @backstage/plugin-tech-insights-backend@0.4.1-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17-next.0 - - @backstage/plugin-tech-insights-node@0.3.1-next.0 - - @backstage/plugin-todo-backend@0.1.30-next.0 - -## 0.2.71 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.13.3 - - @backstage/plugin-auth-backend@0.14.0 - - @backstage/plugin-kubernetes-backend@0.5.1 - - @backstage/plugin-catalog-backend@1.1.2 - - @backstage/plugin-tech-insights-backend@0.4.0 - - @backstage/plugin-scaffolder-backend@1.2.0 - - @backstage/backend-tasks@0.3.1 - - @backstage/integration@1.2.0 - - @backstage/plugin-tech-insights-node@0.3.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16 - - @backstage/plugin-rollbar-backend@0.1.29 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.4 - - @backstage/config@1.0.1 - - @backstage/plugin-app-backend@0.3.32 - - @backstage/plugin-techdocs-backend@1.1.1 - - @backstage/plugin-search-backend-node@0.6.1 - - @backstage/plugin-search-backend-module-pg@0.3.3 - - @backstage/plugin-jenkins-backend@0.1.22 - - @backstage/plugin-search-backend@0.5.2 - - @backstage/plugin-auth-node@0.2.1 - - @backstage/plugin-azure-devops-backend@0.3.11 - - example-app@0.2.71 - - @backstage/catalog-client@1.0.2 - - @backstage/catalog-model@1.0.2 - - @backstage/plugin-badges-backend@0.1.26 - - @backstage/plugin-code-coverage-backend@0.1.30 - - @backstage/plugin-graphql-backend@0.1.22 - - @backstage/plugin-kafka-backend@0.2.25 - - @backstage/plugin-permission-backend@0.5.7 - - @backstage/plugin-permission-common@0.6.1 - - @backstage/plugin-permission-node@0.6.1 - - @backstage/plugin-proxy-backend@0.2.26 - - @backstage/plugin-todo-backend@0.1.29 - -## 0.2.71-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.13.3-next.2 - - @backstage/plugin-kubernetes-backend@0.5.1-next.1 - - @backstage/plugin-catalog-backend@1.1.2-next.2 - - @backstage/backend-tasks@0.3.1-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.4.0-next.1 - - @backstage/plugin-scaffolder-backend@1.2.0-next.1 - - @backstage/config@1.0.1-next.0 - - @backstage/plugin-search-backend-node@0.6.1-next.1 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.4-next.1 - - @backstage/plugin-search-backend-module-pg@0.3.3-next.1 - - @backstage/plugin-azure-devops-backend@0.3.11-next.1 - - example-app@0.2.71-next.2 - - @backstage/catalog-model@1.0.2-next.0 - - @backstage/integration@1.2.0-next.1 - - @backstage/plugin-app-backend@0.3.32-next.1 - - @backstage/plugin-auth-backend@0.13.1-next.2 - - @backstage/plugin-auth-node@0.2.1-next.1 - - @backstage/plugin-badges-backend@0.1.26-next.1 - - @backstage/plugin-code-coverage-backend@0.1.30-next.1 - - @backstage/plugin-graphql-backend@0.1.22-next.1 - - @backstage/plugin-jenkins-backend@0.1.22-next.1 - - @backstage/plugin-kafka-backend@0.2.25-next.1 - - @backstage/plugin-permission-backend@0.5.7-next.1 - - @backstage/plugin-permission-common@0.6.1-next.0 - - @backstage/plugin-permission-node@0.6.1-next.1 - - @backstage/plugin-proxy-backend@0.2.26-next.1 - - @backstage/plugin-rollbar-backend@0.1.29-next.2 - - @backstage/plugin-search-backend@0.5.2-next.1 - - @backstage/plugin-tech-insights-backend@0.4.0-next.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16-next.2 - - @backstage/plugin-tech-insights-node@0.3.0-next.2 - - @backstage/plugin-techdocs-backend@1.1.1-next.1 - - @backstage/plugin-todo-backend@0.1.29-next.1 - - @backstage/catalog-client@1.0.2-next.0 - -## 0.2.71-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.13.1-next.1 - - @backstage/plugin-tech-insights-backend@0.4.0-next.1 - - @backstage/backend-common@0.13.3-next.1 - - @backstage/plugin-tech-insights-node@0.3.0-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16-next.1 - - @backstage/plugin-catalog-backend@1.1.2-next.1 - - @backstage/plugin-rollbar-backend@0.1.29-next.1 - - example-app@0.2.71-next.1 - -## 0.2.71-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.13.3-next.0 - - @backstage/plugin-scaffolder-backend@1.2.0-next.0 - - @backstage/plugin-kubernetes-backend@0.5.1-next.0 - - @backstage/integration@1.2.0-next.0 - - @backstage/plugin-catalog-backend@1.1.2-next.0 - - @backstage/plugin-app-backend@0.3.32-next.0 - - @backstage/plugin-auth-backend@0.13.1-next.0 - - @backstage/plugin-rollbar-backend@0.1.29-next.0 - - @backstage/plugin-techdocs-backend@1.1.1-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.4-next.0 - - @backstage/plugin-jenkins-backend@0.1.22-next.0 - - @backstage/plugin-search-backend@0.5.2-next.0 - - @backstage/backend-tasks@0.3.1-next.0 - - @backstage/plugin-auth-node@0.2.1-next.0 - - example-app@0.2.71-next.0 - - @backstage/plugin-azure-devops-backend@0.3.11-next.0 - - @backstage/plugin-badges-backend@0.1.26-next.0 - - @backstage/plugin-code-coverage-backend@0.1.30-next.0 - - @backstage/plugin-graphql-backend@0.1.22-next.0 - - @backstage/plugin-kafka-backend@0.2.25-next.0 - - @backstage/plugin-permission-backend@0.5.7-next.0 - - @backstage/plugin-permission-node@0.6.1-next.0 - - @backstage/plugin-proxy-backend@0.2.26-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.7-next.0 - - @backstage/plugin-search-backend-module-pg@0.3.3-next.0 - - @backstage/plugin-search-backend-node@0.6.1-next.0 - - @backstage/plugin-tech-insights-backend@0.3.1-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.16-next.0 - - @backstage/plugin-tech-insights-node@0.2.10-next.0 - - @backstage/plugin-todo-backend@0.1.29-next.0 - -## 0.2.70 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.1.0 - - @backstage/plugin-techdocs-backend@1.1.0 - - @backstage/plugin-scaffolder-backend@1.1.0 - - @backstage/integration@1.1.0 - - @backstage/plugin-search-backend@0.5.0 - - @backstage/plugin-auth-backend@0.13.0 - - @backstage/backend-tasks@0.3.0 - - @backstage/plugin-permission-common@0.6.0 - - @backstage/plugin-permission-node@0.6.0 - - @backstage/catalog-model@1.0.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15 - - @backstage/plugin-kafka-backend@0.2.24 - - @backstage/plugin-auth-node@0.2.0 - - @backstage/plugin-jenkins-backend@0.1.20 - - @backstage/plugin-badges-backend@0.1.25 - - @backstage/plugin-tech-insights-node@0.2.9 - - @backstage/plugin-todo-backend@0.1.28 - - @backstage/backend-common@0.13.2 - - @backstage/plugin-kubernetes-backend@0.5.0 - - @backstage/plugin-search-backend-node@0.6.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.3 - - @backstage/plugin-search-backend-module-pg@0.3.2 - - @backstage/plugin-permission-backend@0.5.6 - - @backstage/plugin-tech-insights-backend@0.3.0 - - @backstage/plugin-azure-devops-backend@0.3.10 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.6 - - example-app@0.2.70 - - @backstage/catalog-client@1.0.1 - - @backstage/plugin-app-backend@0.3.31 - - @backstage/plugin-code-coverage-backend@0.1.29 - - @backstage/plugin-graphql-backend@0.1.21 - - @backstage/plugin-proxy-backend@0.2.25 - - @backstage/plugin-rollbar-backend@0.1.28 - -## 0.2.70-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.13.0-next.2 - - @backstage/plugin-catalog-backend@1.1.0-next.3 - - @backstage/plugin-kafka-backend@0.2.24-next.1 - - @backstage/plugin-search-backend@0.5.0-next.2 - - @backstage/plugin-permission-common@0.6.0-next.1 - - @backstage/plugin-permission-node@0.6.0-next.2 - - @backstage/plugin-jenkins-backend@0.1.20-next.2 - - @backstage/plugin-todo-backend@0.1.28-next.2 - - @backstage/backend-common@0.13.2-next.2 - - @backstage/plugin-kubernetes-backend@0.5.0-next.1 - - @backstage/plugin-search-backend-node@0.6.0-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.2 - - @backstage/integration@1.1.0-next.2 - - @backstage/plugin-techdocs-backend@1.1.0-next.2 - - example-app@0.2.70-next.2 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.3-next.1 - - @backstage/plugin-search-backend-module-pg@0.3.2-next.1 - - @backstage/plugin-app-backend@0.3.31-next.1 - -## 0.2.70-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@1.1.0-next.1 - - @backstage/plugin-techdocs-backend@1.0.1-next.1 - - @backstage/plugin-scaffolder-backend@1.1.0-next.1 - - @backstage/integration@1.1.0-next.1 - - @backstage/plugin-search-backend@0.5.0-next.1 - - @backstage/backend-tasks@0.3.0-next.1 - - @backstage/plugin-permission-common@0.6.0-next.0 - - @backstage/plugin-permission-node@0.6.0-next.1 - - @backstage/plugin-badges-backend@0.1.25-next.1 - - @backstage/plugin-tech-insights-node@0.2.9-next.1 - - @backstage/plugin-permission-backend@0.5.6-next.1 - - @backstage/backend-common@0.13.2-next.1 - - @backstage/plugin-auth-backend@0.13.0-next.1 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15-next.1 - - @backstage/plugin-tech-insights-backend@0.3.0-next.1 - - @backstage/plugin-jenkins-backend@0.1.20-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.1 - - @backstage/plugin-code-coverage-backend@0.1.29-next.1 - - @backstage/plugin-todo-backend@0.1.28-next.1 - - example-app@0.2.70-next.1 - -## 0.2.70-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/catalog-model@1.0.1-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.15-next.0 - - @backstage/plugin-search-backend@0.5.0-next.0 - - @backstage/plugin-auth-node@0.2.0-next.0 - - @backstage/plugin-auth-backend@0.13.0-next.0 - - @backstage/plugin-catalog-backend@1.0.1-next.0 - - @backstage/plugin-search-backend-node@0.5.3-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.3-next.0 - - @backstage/plugin-search-backend-module-pg@0.3.2-next.0 - - @backstage/backend-common@0.13.2-next.0 - - @backstage/integration@1.0.1-next.0 - - @backstage/plugin-tech-insights-backend@0.2.11-next.0 - - @backstage/plugin-techdocs-backend@1.0.1-next.0 - - @backstage/plugin-jenkins-backend@0.1.20-next.0 - - example-app@0.2.70-next.0 - - @backstage/catalog-client@1.0.1-next.0 - - @backstage/plugin-badges-backend@0.1.25-next.0 - - @backstage/plugin-code-coverage-backend@0.1.29-next.0 - - @backstage/plugin-kafka-backend@0.2.24-next.0 - - @backstage/plugin-kubernetes-backend@0.4.14-next.0 - - @backstage/plugin-scaffolder-backend@1.0.1-next.0 - - @backstage/plugin-todo-backend@0.1.28-next.0 - - @backstage/plugin-app-backend@0.3.31-next.0 - - @backstage/plugin-permission-backend@0.5.6-next.0 - - @backstage/plugin-permission-node@0.5.6-next.0 - - @backstage/backend-tasks@0.2.2-next.0 - - @backstage/plugin-azure-devops-backend@0.3.10-next.0 - - @backstage/plugin-graphql-backend@0.1.21-next.0 - - @backstage/plugin-proxy-backend@0.2.25-next.0 - - @backstage/plugin-rollbar-backend@0.1.28-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.6-next.0 - - @backstage/plugin-tech-insights-node@0.2.9-next.0 - -## 0.2.69 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-app-backend@0.3.30 - - @backstage/plugin-azure-devops-backend@0.3.9 - - @backstage/plugin-badges-backend@0.1.24 - - @backstage/plugin-catalog-backend@1.0.0 - - @backstage/plugin-jenkins-backend@0.1.19 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.5 - - @backstage/plugin-tech-insights-backend@0.2.10 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.14 - - @backstage/plugin-todo-backend@0.1.27 - - @backstage/plugin-kubernetes-backend@0.4.13 - - @backstage/plugin-scaffolder-backend@1.0.0 - - @backstage/backend-common@0.13.1 - - @backstage/backend-tasks@0.2.1 - - @backstage/plugin-auth-backend@0.12.2 - - @backstage/plugin-code-coverage-backend@0.1.28 - - @backstage/catalog-model@1.0.0 - - @backstage/integration@1.0.0 - - @backstage/catalog-client@1.0.0 - - @backstage/config@1.0.0 - - @backstage/plugin-techdocs-backend@1.0.0 - - @backstage/plugin-permission-common@0.5.3 - - @backstage/plugin-search-backend-node@0.5.2 - - example-app@0.2.69 - - @backstage/plugin-auth-node@0.1.6 - - @backstage/plugin-graphql-backend@0.1.20 - - @backstage/plugin-kafka-backend@0.2.23 - - @backstage/plugin-permission-backend@0.5.5 - - @backstage/plugin-permission-node@0.5.5 - - @backstage/plugin-proxy-backend@0.2.24 - - @backstage/plugin-rollbar-backend@0.1.27 - - @backstage/plugin-search-backend@0.4.8 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.2 - - @backstage/plugin-tech-insights-node@0.2.8 - -## 0.2.68 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.13.0 - - @backstage/backend-tasks@0.2.0 - - @backstage/plugin-app-backend@0.3.29 - - @backstage/plugin-auth-backend@0.12.1 - - @backstage/plugin-catalog-backend@0.24.0 - - @backstage/plugin-scaffolder-backend@0.18.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.4 - - @backstage/plugin-kubernetes-backend@0.4.12 - - @backstage/plugin-rollbar-backend@0.1.26 - - @backstage/plugin-techdocs-backend@0.14.2 - - @backstage/catalog-model@0.13.0 - - @backstage/plugin-badges-backend@0.1.23 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.1 - - @backstage/plugin-search-backend-module-pg@0.3.1 - - @backstage/plugin-search-backend-node@0.5.1 - - @backstage/plugin-search-backend@0.4.7 - - @backstage/catalog-client@0.9.0 - - example-app@0.2.68 - - @backstage/plugin-auth-node@0.1.5 - - @backstage/plugin-azure-devops-backend@0.3.8 - - @backstage/plugin-code-coverage-backend@0.1.27 - - @backstage/plugin-graphql-backend@0.1.19 - - @backstage/plugin-jenkins-backend@0.1.18 - - @backstage/plugin-kafka-backend@0.2.22 - - @backstage/plugin-permission-backend@0.5.4 - - @backstage/plugin-permission-node@0.5.4 - - @backstage/plugin-proxy-backend@0.2.23 - - @backstage/plugin-tech-insights-backend@0.2.9 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.13 - - @backstage/plugin-tech-insights-node@0.2.7 - - @backstage/plugin-todo-backend@0.1.26 - -## 0.2.68-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.13.0-next.0 - - @backstage/backend-tasks@0.2.0-next.0 - - @backstage/plugin-app-backend@0.3.29-next.0 - - @backstage/plugin-auth-backend@0.12.1-next.0 - - @backstage/plugin-catalog-backend@0.24.0-next.0 - - @backstage/plugin-scaffolder-backend@0.18.0-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.4-next.0 - - @backstage/plugin-kubernetes-backend@0.4.12-next.0 - - @backstage/plugin-rollbar-backend@0.1.26-next.0 - - @backstage/plugin-techdocs-backend@0.14.2-next.0 - - @backstage/catalog-model@0.13.0-next.0 - - @backstage/plugin-badges-backend@0.1.23-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.1-next.0 - - @backstage/plugin-search-backend-module-pg@0.3.1-next.0 - - @backstage/plugin-search-backend-node@0.5.1-next.0 - - @backstage/plugin-search-backend@0.4.7-next.0 - - @backstage/catalog-client@0.9.0-next.0 - - @backstage/plugin-auth-node@0.1.5-next.0 - - @backstage/plugin-azure-devops-backend@0.3.8-next.0 - - @backstage/plugin-code-coverage-backend@0.1.27-next.0 - - @backstage/plugin-graphql-backend@0.1.19-next.0 - - @backstage/plugin-jenkins-backend@0.1.18-next.0 - - @backstage/plugin-kafka-backend@0.2.22-next.0 - - @backstage/plugin-permission-backend@0.5.4-next.0 - - @backstage/plugin-permission-node@0.5.4-next.0 - - @backstage/plugin-proxy-backend@0.2.23-next.0 - - @backstage/plugin-tech-insights-backend@0.2.9-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.13-next.0 - - @backstage/plugin-tech-insights-node@0.2.7-next.0 - - @backstage/plugin-todo-backend@0.1.26-next.0 - - example-app@0.2.68-next.0 - -## 0.2.67 - -### Patch Changes - -- Updated dependencies - - @backstage/catalog-model@0.12.0 - - @backstage/catalog-client@0.8.0 - - @backstage/plugin-catalog-backend@0.23.0 - - @backstage/backend-common@0.12.0 - - @backstage/plugin-scaffolder-backend@0.17.3 - - @backstage/plugin-techdocs-backend@0.14.1 - - @backstage/plugin-auth-backend@0.12.0 - - @backstage/plugin-badges-backend@0.1.22 - - @backstage/plugin-code-coverage-backend@0.1.26 - - @backstage/plugin-jenkins-backend@0.1.17 - - @backstage/plugin-todo-backend@0.1.25 - - @backstage/integration@0.8.0 - - @backstage/plugin-permission-common@0.5.2 - - @backstage/plugin-permission-node@0.5.3 - - @backstage/plugin-search-backend-node@0.5.0 - - @backstage/plugin-search-backend-module-pg@0.3.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.1.0 - - @backstage/plugin-tech-insights-backend@0.2.8 - - example-app@0.2.67 - - @backstage/plugin-auth-node@0.1.4 - - @backstage/plugin-kafka-backend@0.2.21 - - @backstage/plugin-kubernetes-backend@0.4.11 - - @backstage/backend-tasks@0.1.10 - - @backstage/plugin-app-backend@0.3.28 - - @backstage/plugin-azure-devops-backend@0.3.7 - - @backstage/plugin-graphql-backend@0.1.18 - - @backstage/plugin-permission-backend@0.5.3 - - @backstage/plugin-proxy-backend@0.2.22 - - @backstage/plugin-rollbar-backend@0.1.25 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.3 - - @backstage/plugin-search-backend@0.4.6 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.12 - - @backstage/plugin-tech-insights-node@0.2.6 - -## 0.2.66 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.11.0 - - @backstage/plugin-catalog-backend@0.22.0 - - @backstage/plugin-scaffolder-backend@0.17.0 - - @backstage/plugin-graphql-backend@0.1.17 - - @backstage/plugin-auth-backend@0.11.0 - - @backstage/plugin-kubernetes-backend@0.4.10 - - @backstage/plugin-code-coverage-backend@0.1.25 - - @backstage/plugin-jenkins-backend@0.1.16 - - @backstage/plugin-tech-insights-backend@0.2.7 - - @backstage/plugin-todo-backend@0.1.24 - - @backstage/catalog-model@0.11.0 - - @backstage/catalog-client@0.7.2 - - @backstage/plugin-badges-backend@0.1.21 - - @backstage/backend-tasks@0.1.9 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.2 - - @backstage/plugin-techdocs-backend@0.14.0 - - @backstage/plugin-permission-node@0.5.2 - - @backstage/integration@0.7.5 - - example-app@0.2.66 - - @backstage/plugin-app-backend@0.3.27 - - @backstage/plugin-auth-node@0.1.3 - - @backstage/plugin-azure-devops-backend@0.3.6 - - @backstage/plugin-kafka-backend@0.2.20 - - @backstage/plugin-permission-backend@0.5.2 - - @backstage/plugin-proxy-backend@0.2.21 - - @backstage/plugin-rollbar-backend@0.1.24 - - @backstage/plugin-search-backend@0.4.5 - - @backstage/plugin-search-backend-module-pg@0.2.9 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.11 - - @backstage/plugin-tech-insights-node@0.2.5 - -## 0.2.66 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.10.9 - - @backstage/backend-tasks@0.1.8 - - @backstage/catalog-client@0.7.1 - - @backstage/catalog-model@0.10.1 - - @backstage/config@0.1.15 - - @backstage/integration@0.7.4 - - @backstage/plugin-app-backend@0.3.26 - - @backstage/plugin-auth-backend@0.10.2 - - @backstage/plugin-auth-node@0.1.2 - - @backstage/plugin-azure-devops-backend@0.3.5 - - @backstage/plugin-badges-backend@0.1.20 - - @backstage/plugin-catalog-backend@0.21.5 - - @backstage/plugin-code-coverage-backend@0.1.24 - - @backstage/plugin-graphql-backend@0.1.16 - - @backstage/plugin-jenkins-backend@0.1.15 - - @backstage/plugin-kafka-backend@0.2.19 - - @backstage/plugin-kubernetes-backend@0.4.9 - - @backstage/plugin-permission-backend@0.5.1 - - @backstage/plugin-permission-common@0.5.1 - - @backstage/plugin-permission-node@0.5.1 - - @backstage/plugin-proxy-backend@0.2.20 - - @backstage/plugin-rollbar-backend@0.1.23 - - @backstage/plugin-scaffolder-backend@0.16.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.1 - - @backstage/plugin-search-backend@0.4.4 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.10 - - @backstage/plugin-search-backend-module-pg@0.2.8 - - @backstage/plugin-search-backend-node@0.4.7 - - @backstage/plugin-tech-insights-backend@0.2.6 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.10 - - @backstage/plugin-tech-insights-node@0.2.4 - - @backstage/plugin-techdocs-backend@0.13.5 - - @backstage/plugin-todo-backend@0.1.23 - -## 0.2.65 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-techdocs-backend@0.13.4 - - @backstage/plugin-catalog-backend@0.21.4 - - @backstage/backend-common@0.10.8 - - @backstage/catalog-client@0.7.0 - - @backstage/integration@0.7.3 - - @backstage/plugin-auth-backend@0.10.1 - - @backstage/plugin-auth-node@0.1.1 - - @backstage/plugin-permission-backend@0.5.0 - - @backstage/plugin-permission-common@0.5.0 - - @backstage/plugin-rollbar-backend@0.1.22 - - @backstage/plugin-scaffolder-backend@0.16.0 - - @backstage/backend-tasks@0.1.7 - - @backstage/catalog-model@0.10.0 - - @backstage/config@0.1.14 - - @backstage/plugin-app-backend@0.3.25 - - @backstage/plugin-azure-devops-backend@0.3.4 - - @backstage/plugin-badges-backend@0.1.19 - - @backstage/plugin-code-coverage-backend@0.1.23 - - @backstage/plugin-graphql-backend@0.1.15 - - @backstage/plugin-jenkins-backend@0.1.14 - - @backstage/plugin-kafka-backend@0.2.18 - - @backstage/plugin-kubernetes-backend@0.4.8 - - @backstage/plugin-permission-node@0.5.0 - - @backstage/plugin-proxy-backend@0.2.19 - - @backstage/plugin-scaffolder-backend-module-rails@0.3.0 - - @backstage/plugin-search-backend@0.4.3 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.9 - - @backstage/plugin-search-backend-module-pg@0.2.7 - - @backstage/plugin-search-backend-node@0.4.6 - - @backstage/plugin-tech-insights-backend@0.2.5 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.9 - - @backstage/plugin-tech-insights-node@0.2.3 - - @backstage/plugin-todo-backend@0.1.22 - - example-app@0.2.65 - -## 0.2.64 - -### Patch Changes - -- Updated dependencies - - @backstage/catalog-client@0.6.0 - - @backstage/plugin-auth-backend@0.10.0 - - @backstage/backend-common@0.10.7 - - @backstage/backend-tasks@0.1.6 - - @backstage/plugin-app-backend@0.3.24 - - @backstage/plugin-catalog-backend@0.21.3 - - @backstage/plugin-code-coverage-backend@0.1.22 - - @backstage/plugin-scaffolder-backend@0.15.24 - - @backstage/plugin-search-backend-module-pg@0.2.6 - - @backstage/plugin-tech-insights-backend@0.2.4 - - @backstage/plugin-techdocs-backend@0.13.3 - - @backstage/plugin-auth-node@0.1.0 - - @backstage/plugin-permission-backend@0.4.3 - - @backstage/plugin-search-backend@0.4.2 - - @backstage/plugin-badges-backend@0.1.18 - - @backstage/plugin-jenkins-backend@0.1.13 - - @backstage/plugin-todo-backend@0.1.21 - - @backstage/plugin-permission-node@0.4.3 - - example-app@0.2.64 - - @backstage/plugin-azure-devops-backend@0.3.3 - - @backstage/plugin-graphql-backend@0.1.14 - - @backstage/plugin-kafka-backend@0.2.17 - - @backstage/plugin-kubernetes-backend@0.4.7 - - @backstage/plugin-proxy-backend@0.2.18 - - @backstage/plugin-rollbar-backend@0.1.21 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.6 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.8 - - @backstage/plugin-tech-insights-node@0.2.2 - -## 0.2.64-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.10.0-next.0 - - @backstage/backend-common@0.10.7-next.0 - - @backstage/backend-tasks@0.1.6-next.0 - - @backstage/plugin-app-backend@0.3.24-next.0 - - @backstage/plugin-catalog-backend@0.21.3-next.0 - - @backstage/plugin-code-coverage-backend@0.1.22-next.0 - - @backstage/plugin-scaffolder-backend@0.15.24-next.0 - - @backstage/plugin-search-backend-module-pg@0.2.6-next.0 - - @backstage/plugin-tech-insights-backend@0.2.4-next.0 - - @backstage/plugin-techdocs-backend@0.13.3-next.0 - - example-app@0.2.64-next.0 - - @backstage/plugin-azure-devops-backend@0.3.3-next.0 - - @backstage/plugin-badges-backend@0.1.18-next.0 - - @backstage/plugin-graphql-backend@0.1.14-next.0 - - @backstage/plugin-jenkins-backend@0.1.13-next.0 - - @backstage/plugin-kafka-backend@0.2.17-next.0 - - @backstage/plugin-kubernetes-backend@0.4.7-next.0 - - @backstage/plugin-permission-backend@0.4.3-next.0 - - @backstage/plugin-permission-node@0.4.3-next.0 - - @backstage/plugin-proxy-backend@0.2.18-next.0 - - @backstage/plugin-rollbar-backend@0.1.21-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.6-next.0 - - @backstage/plugin-search-backend@0.4.2-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.8-next.0 - - @backstage/plugin-tech-insights-node@0.2.2-next.0 - - @backstage/plugin-todo-backend@0.1.21-next.0 - -## 0.2.63 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.9.0 - - @backstage/plugin-rollbar-backend@0.1.20 - - @backstage/plugin-catalog-backend@0.21.2 - - @backstage/plugin-scaffolder-backend@0.15.23 - - @backstage/plugin-proxy-backend@0.2.17 - - @backstage/backend-common@0.10.6 - - example-app@0.2.63 - - @backstage/backend-tasks@0.1.5 - - @backstage/plugin-app-backend@0.3.23 - - @backstage/plugin-azure-devops-backend@0.3.2 - - @backstage/plugin-badges-backend@0.1.17 - - @backstage/plugin-code-coverage-backend@0.1.21 - - @backstage/plugin-graphql-backend@0.1.13 - - @backstage/plugin-jenkins-backend@0.1.12 - - @backstage/plugin-kafka-backend@0.2.16 - - @backstage/plugin-kubernetes-backend@0.4.6 - - @backstage/plugin-permission-backend@0.4.2 - - @backstage/plugin-permission-node@0.4.2 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.5 - - @backstage/plugin-search-backend@0.4.1 - - @backstage/plugin-search-backend-module-pg@0.2.5 - - @backstage/plugin-tech-insights-backend@0.2.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.7 - - @backstage/plugin-tech-insights-node@0.2.1 - - @backstage/plugin-techdocs-backend@0.13.2 - - @backstage/plugin-todo-backend@0.1.20 - -## 0.2.63-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.9.0-next.1 - - @backstage/backend-common@0.10.6-next.0 - - example-app@0.2.63-next.1 - - @backstage/plugin-catalog-backend@0.21.2-next.1 - - @backstage/plugin-techdocs-backend@0.13.2-next.0 - - @backstage/backend-tasks@0.1.5-next.0 - - @backstage/plugin-app-backend@0.3.23-next.0 - - @backstage/plugin-azure-devops-backend@0.3.2-next.0 - - @backstage/plugin-badges-backend@0.1.17-next.0 - - @backstage/plugin-code-coverage-backend@0.1.21-next.0 - - @backstage/plugin-graphql-backend@0.1.13-next.0 - - @backstage/plugin-jenkins-backend@0.1.12-next.0 - - @backstage/plugin-kafka-backend@0.2.16-next.0 - - @backstage/plugin-kubernetes-backend@0.4.6-next.0 - - @backstage/plugin-permission-backend@0.4.2-next.1 - - @backstage/plugin-permission-node@0.4.2-next.1 - - @backstage/plugin-proxy-backend@0.2.17-next.1 - - @backstage/plugin-rollbar-backend@0.1.20-next.1 - - @backstage/plugin-scaffolder-backend@0.15.23-next.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.5-next.1 - - @backstage/plugin-search-backend@0.4.1-next.1 - - @backstage/plugin-search-backend-module-pg@0.2.5-next.0 - - @backstage/plugin-tech-insights-backend@0.2.3-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.7-next.0 - - @backstage/plugin-tech-insights-node@0.2.1-next.0 - - @backstage/plugin-todo-backend@0.1.20-next.0 - -## 0.2.63-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.9.0-next.0 - - @backstage/plugin-rollbar-backend@0.1.20-next.0 - - @backstage/plugin-catalog-backend@0.21.2-next.0 - - @backstage/plugin-scaffolder-backend@0.15.23-next.0 - - @backstage/plugin-proxy-backend@0.2.17-next.0 - - @backstage/plugin-permission-backend@0.4.2-next.0 - - @backstage/plugin-permission-node@0.4.2-next.0 - - @backstage/plugin-search-backend@0.4.1-next.0 - - example-app@0.2.63-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.5-next.0 - -## 0.2.62 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-search-backend-node@0.4.5 - - @backstage/plugin-catalog-backend@0.21.1 - - @backstage/plugin-scaffolder-backend@0.15.22 - - @backstage/plugin-kubernetes-backend@0.4.5 - - @backstage/plugin-auth-backend@0.8.0 - - @backstage/plugin-search-backend@0.4.0 - - @backstage/plugin-tech-insights-backend@0.2.2 - - @backstage/plugin-techdocs-backend@0.13.1 - - @backstage/backend-common@0.10.5 - - example-app@0.2.62 - - @backstage/plugin-permission-backend@0.4.1 - - @backstage/plugin-permission-node@0.4.1 - -## 0.2.61 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.7.0 - - @backstage/plugin-permission-backend@0.4.0 - - @backstage/plugin-catalog-backend@0.21.0 - - @backstage/plugin-kubernetes-backend@0.4.4 - - @backstage/integration@0.7.2 - - @backstage/plugin-permission-common@0.4.0 - - @backstage/plugin-search-backend@0.3.1 - - @backstage/plugin-techdocs-backend@0.13.0 - - @backstage/backend-common@0.10.4 - - @backstage/config@0.1.13 - - @backstage/plugin-app-backend@0.3.22 - - @backstage/plugin-permission-node@0.4.0 - - @backstage/plugin-scaffolder-backend@0.15.21 - - @backstage/plugin-tech-insights-backend@0.2.0 - - @backstage/plugin-tech-insights-node@0.2.0 - - @backstage/catalog-model@0.9.10 - - example-app@0.2.61 - - @backstage/backend-tasks@0.1.4 - - @backstage/catalog-client@0.5.5 - - @backstage/plugin-azure-devops-backend@0.3.1 - - @backstage/plugin-badges-backend@0.1.16 - - @backstage/plugin-code-coverage-backend@0.1.20 - - @backstage/plugin-graphql-backend@0.1.12 - - @backstage/plugin-jenkins-backend@0.1.11 - - @backstage/plugin-kafka-backend@0.2.15 - - @backstage/plugin-proxy-backend@0.2.16 - - @backstage/plugin-rollbar-backend@0.1.19 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.4 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.8 - - @backstage/plugin-search-backend-module-pg@0.2.4 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.6 - - @backstage/plugin-todo-backend@0.1.19 - -## 0.2.61-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.7.0-next.0 - - @backstage/plugin-permission-backend@0.4.0-next.0 - - @backstage/plugin-catalog-backend@0.21.0-next.0 - - @backstage/plugin-permission-common@0.4.0-next.0 - - @backstage/backend-common@0.10.4-next.0 - - @backstage/config@0.1.13-next.0 - - @backstage/plugin-app-backend@0.3.22-next.0 - - @backstage/plugin-permission-node@0.4.0-next.0 - - @backstage/plugin-tech-insights-backend@0.2.0-next.0 - - @backstage/plugin-tech-insights-node@0.2.0-next.0 - - @backstage/catalog-model@0.9.10-next.0 - - example-app@0.2.61-next.0 - - @backstage/plugin-scaffolder-backend@0.15.21-next.0 - - @backstage/backend-tasks@0.1.4-next.0 - - @backstage/catalog-client@0.5.5-next.0 - - @backstage/integration@0.7.2-next.0 - - @backstage/plugin-azure-devops-backend@0.3.1-next.0 - - @backstage/plugin-badges-backend@0.1.16-next.0 - - @backstage/plugin-code-coverage-backend@0.1.20-next.0 - - @backstage/plugin-graphql-backend@0.1.12-next.0 - - @backstage/plugin-jenkins-backend@0.1.11-next.0 - - @backstage/plugin-kafka-backend@0.2.15-next.0 - - @backstage/plugin-kubernetes-backend@0.4.4-next.0 - - @backstage/plugin-proxy-backend@0.2.16-next.0 - - @backstage/plugin-rollbar-backend@0.1.19-next.0 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.4-next.0 - - @backstage/plugin-search-backend@0.3.1-next.0 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.8-next.0 - - @backstage/plugin-search-backend-module-pg@0.2.4-next.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.6-next.0 - - @backstage/plugin-techdocs-backend@0.12.4-next.0 - - @backstage/plugin-todo-backend@0.1.19-next.0 - -## 0.2.60 - -### Patch Changes - -- Updated dependencies - - @backstage/config@0.1.12 - - @backstage/plugin-scaffolder-backend@0.15.20 - - @backstage/integration@0.7.1 - - @backstage/backend-common@0.10.3 - - @backstage/plugin-todo-backend@0.1.18 - - @backstage/plugin-catalog-backend@0.20.0 - - @backstage/plugin-tech-insights-backend@0.1.5 - - @backstage/plugin-permission-node@0.3.0 - - @backstage/plugin-auth-backend@0.6.2 - - @backstage/plugin-code-coverage-backend@0.1.19 - - @backstage/plugin-search-backend-node@0.4.4 - - @backstage/plugin-techdocs-backend@0.12.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.5 - - @backstage/plugin-permission-backend@0.3.0 - - @backstage/plugin-graphql-backend@0.1.11 - - @backstage/plugin-kubernetes-backend@0.4.3 - - example-app@0.2.60 - - @backstage/backend-tasks@0.1.3 - - @backstage/catalog-client@0.5.4 - - @backstage/catalog-model@0.9.9 - - @backstage/plugin-badges-backend@0.1.15 - - @backstage/plugin-kafka-backend@0.2.14 - - @backstage/plugin-permission-common@0.3.1 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.3 - -## 0.2.59 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-rollbar-backend@0.1.18 - - @backstage/plugin-auth-backend@0.6.0 - - @backstage/backend-common@0.10.1 - - @backstage/plugin-app-backend@0.3.21 - - @backstage/plugin-catalog-backend@0.19.4 - - @backstage/plugin-scaffolder-backend@0.15.19 - - @backstage/integration@0.7.0 - - @backstage/plugin-techdocs-backend@0.12.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.4 - - @backstage/plugin-permission-backend@0.2.3 - - @backstage/plugin-permission-node@0.2.3 - - @backstage/plugin-code-coverage-backend@0.1.18 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.2 - - @backstage/plugin-todo-backend@0.1.17 - -## 0.2.58 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.10.0 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.1 - - @backstage/catalog-client@0.5.3 - - @backstage/plugin-rollbar-backend@0.1.17 - - @backstage/plugin-auth-backend@0.5.2 - - @backstage/plugin-permission-common@0.3.0 - - @backstage/plugin-search-backend@0.3.0 - - @backstage/plugin-techdocs-backend@0.12.1 - - @backstage/plugin-jenkins-backend@0.1.10 - - @backstage/plugin-permission-node@0.2.2 - - example-app@0.2.58 - - @backstage/plugin-app-backend@0.3.20 - - @backstage/plugin-azure-devops-backend@0.2.6 - - @backstage/plugin-badges-backend@0.1.14 - - @backstage/plugin-catalog-backend@0.19.3 - - @backstage/plugin-code-coverage-backend@0.1.17 - - @backstage/plugin-graphql-backend@0.1.10 - - @backstage/plugin-kafka-backend@0.2.13 - - @backstage/plugin-kubernetes-backend@0.4.1 - - @backstage/plugin-permission-backend@0.2.2 - - @backstage/plugin-proxy-backend@0.2.15 - - @backstage/plugin-scaffolder-backend@0.15.18 - - @backstage/plugin-search-backend-module-pg@0.2.3 - - @backstage/plugin-tech-insights-backend@0.1.4 - - @backstage/plugin-tech-insights-node@0.1.2 - - @backstage/plugin-todo-backend@0.1.16 - -## 0.2.57 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-search-backend-module-elasticsearch@0.0.7 - - @backstage/plugin-catalog-backend@0.19.2 - - @backstage/plugin-scaffolder-backend@0.15.17 - - @backstage/backend-common@0.9.14 - - @backstage/plugin-azure-devops-backend@0.2.5 - - @backstage/plugin-auth-backend@0.5.1 - - @backstage/catalog-model@0.9.8 - - example-app@0.2.57 - -## 0.2.56 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.5.0 - - @backstage/plugin-scaffolder-backend@0.15.16 - - @backstage/plugin-kubernetes-backend@0.4.0 - - @backstage/backend-common@0.9.13 - - @backstage/plugin-catalog-backend@0.19.1 - - @backstage/plugin-search-backend@0.2.8 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.6 - - @backstage/plugin-search-backend-module-pg@0.2.2 - - @backstage/plugin-techdocs-backend@0.12.0 - - @backstage/plugin-todo-backend@0.1.15 - - @backstage/plugin-scaffolder-backend-module-rails@0.2.0 - - @backstage/plugin-azure-devops-backend@0.2.4 - - example-app@0.2.56 - -## 0.2.55 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@0.6.10 - - @backstage/plugin-scaffolder-backend@0.15.15 - - @backstage/plugin-auth-backend@0.4.10 - - @backstage/plugin-kubernetes-backend@0.3.20 - - @backstage/plugin-badges-backend@0.1.13 - - @backstage/plugin-catalog-backend@0.19.0 - - @backstage/plugin-code-coverage-backend@0.1.16 - - @backstage/plugin-jenkins-backend@0.1.9 - - @backstage/plugin-tech-insights-backend@0.1.3 - - @backstage/plugin-techdocs-backend@0.11.0 - - @backstage/plugin-todo-backend@0.1.14 - - @backstage/backend-common@0.9.12 - - @backstage/plugin-azure-devops-backend@0.2.3 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.2 - - @backstage/plugin-tech-insights-node@0.1.1 - - example-app@0.2.55 - -## 0.2.54 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.3.19 - - @backstage/plugin-tech-insights-backend@0.1.2 - - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.1 - - @backstage/plugin-auth-backend@0.4.9 - - @backstage/plugin-scaffolder-backend@0.15.14 - - @backstage/plugin-catalog-backend@0.18.0 - - @backstage/plugin-kafka-backend@0.2.12 - - @backstage/backend-common@0.9.11 - - @backstage/plugin-azure-devops-backend@0.2.2 - - @backstage/plugin-badges-backend@0.1.12 - - @backstage/plugin-code-coverage-backend@0.1.15 - - @backstage/plugin-jenkins-backend@0.1.8 - - @backstage/plugin-proxy-backend@0.2.14 - - @backstage/plugin-rollbar-backend@0.1.16 - - @backstage/plugin-search-backend@0.2.7 - - @backstage/plugin-techdocs-backend@0.10.9 - -## 0.2.52 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.9.9 - - @backstage/plugin-jenkins-backend@0.1.7 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.5 - - @backstage/plugin-scaffolder-backend@0.15.12 - - @backstage/plugin-azure-devops-backend@0.2.0 - - @backstage/catalog-client@0.5.1 - - @backstage/plugin-auth-backend@0.4.7 - - @backstage/plugin-catalog-backend@0.17.3 - - @backstage/plugin-scaffolder-backend-module-rails@0.1.7 - -## 0.2.50 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.4.4 - - @backstage/integration@0.6.8 - - @backstage/plugin-scaffolder-backend@0.15.8 - - @backstage/plugin-catalog-backend@0.17.0 - - @backstage/plugin-azure-devops-backend@0.1.2 - - @backstage/plugin-code-coverage-backend@0.1.13 - - @backstage/plugin-kubernetes-backend@0.3.17 - - example-app@0.2.50 - -## 0.2.49 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@0.16.0 - - @backstage/catalog-model@0.9.4 - - @backstage/plugin-proxy-backend@0.2.13 - - @backstage/plugin-auth-backend@0.4.3 - - @backstage/backend-common@0.9.6 - - @backstage/catalog-client@0.5.0 - - @backstage/integration@0.6.7 - - @backstage/plugin-scaffolder-backend@0.15.7 - - example-app@0.2.49 - - @backstage/plugin-badges-backend@0.1.11 - - @backstage/plugin-code-coverage-backend@0.1.12 - - @backstage/plugin-jenkins-backend@0.1.6 - - @backstage/plugin-techdocs-backend@0.10.4 - - @backstage/plugin-todo-backend@0.1.13 - -## 0.2.48 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.9.5 - - @backstage/plugin-catalog-backend@0.15.0 - - @backstage/plugin-azure-devops-backend@0.1.1 - - @backstage/integration@0.6.6 - - @backstage/plugin-auth-backend@0.4.2 - - example-app@0.2.48 - -## 0.2.47 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@0.14.0 - - @backstage/integration@0.6.5 - - @backstage/catalog-client@0.4.0 - - @backstage/catalog-model@0.9.3 - - @backstage/backend-common@0.9.4 - - @backstage/config@0.1.10 - - @backstage/plugin-kafka-backend@0.2.10 - - @backstage/plugin-kubernetes-backend@0.3.16 - - @backstage/plugin-rollbar-backend@0.1.15 - - @backstage/plugin-search-backend-module-pg@0.2.1 - - example-app@0.2.47 - - @backstage/plugin-auth-backend@0.4.1 - - @backstage/plugin-badges-backend@0.1.10 - - @backstage/plugin-code-coverage-backend@0.1.11 - - @backstage/plugin-jenkins-backend@0.1.5 - - @backstage/plugin-scaffolder-backend@0.15.6 - - @backstage/plugin-techdocs-backend@0.10.3 - - @backstage/plugin-todo-backend@0.1.12 - -## 0.2.46 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.4.0 - - @backstage/plugin-scaffolder-backend@0.15.5 - - @backstage/backend-common@0.9.3 - - @backstage/plugin-catalog-backend@0.13.8 - - @backstage/plugin-techdocs-backend@0.10.2 - - @backstage/integration@0.6.4 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.4 - - example-app@0.2.46 - -## 0.2.44 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-catalog-backend@0.13.6 - - @backstage/plugin-scaffolder-backend@0.15.3 - - @backstage/plugin-techdocs-backend@0.10.1 - - @backstage/plugin-auth-backend@0.3.24 - - @backstage/integration@0.6.3 - - @backstage/plugin-search-backend@0.2.6 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.3 - - @backstage/plugin-search-backend-module-pg@0.2.0 - - @backstage/plugin-search-backend-node@0.4.2 - - @backstage/catalog-model@0.9.1 - - @backstage/backend-common@0.9.1 - - example-app@0.2.44 - -## 0.2.43 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.9.0 - - @backstage/plugin-catalog-backend@0.13.5 - - @backstage/plugin-search-backend-module-pg@0.1.3 - - @backstage/plugin-auth-backend@0.3.23 - - @backstage/plugin-scaffolder-backend@0.15.2 - - @backstage/integration@0.6.2 - - @backstage/config@0.1.8 - - @backstage/plugin-kubernetes-backend@0.3.15 - - @backstage/plugin-techdocs-backend@0.10.0 - - @backstage/plugin-jenkins-backend@0.1.4 - - @backstage/plugin-app-backend@0.3.16 - - @backstage/plugin-badges-backend@0.1.9 - - @backstage/plugin-code-coverage-backend@0.1.10 - - @backstage/plugin-graphql-backend@0.1.9 - - @backstage/plugin-kafka-backend@0.2.9 - - @backstage/plugin-proxy-backend@0.2.12 - - @backstage/plugin-rollbar-backend@0.1.14 - - @backstage/plugin-scaffolder-backend-module-rails@0.1.5 - - @backstage/plugin-search-backend@0.2.5 - - @backstage/plugin-todo-backend@0.1.11 - - example-app@0.2.43 - -## 0.2.41 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-backend@0.3.20 - - @backstage/integration@0.6.0 - - @backstage/plugin-scaffolder-backend@0.15.0 - - @backstage/backend-common@0.8.9 - - @backstage/plugin-kubernetes-backend@0.3.14 - - @backstage/plugin-search-backend-module-elasticsearch@0.0.2 - - @backstage/plugin-search-backend-module-pg@0.1.1 - - @backstage/plugin-catalog-backend@0.13.2 - - @backstage/plugin-code-coverage-backend@0.1.9 - - @backstage/plugin-scaffolder-backend-module-rails@0.1.4 - - @backstage/plugin-techdocs-backend@0.9.2 - - @backstage/plugin-todo-backend@0.1.9 - - example-app@0.2.41 - -## 0.2.38 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-kubernetes-backend@0.3.11 - - @backstage/catalog-client@0.3.17 - - @backstage/plugin-auth-backend@0.3.18 - - @backstage/plugin-jenkins-backend@0.1.2 - - @backstage/backend-common@0.8.7 - - @backstage/plugin-techdocs-backend@0.9.0 - - @backstage/plugin-scaffolder-backend@0.14.1 - -## 0.2.37 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.8.6 - - @backstage/plugin-scaffolder-backend@0.14.0 - - @backstage/plugin-catalog-backend@0.13.0 - - @backstage/plugin-auth-backend@0.3.17 - - @backstage/plugin-scaffolder-backend-module-rails@0.1.3 - - @backstage/plugin-search-backend-node@0.4.0 - - @backstage/plugin-techdocs-backend@0.8.7 - - @backstage/plugin-app-backend@0.3.15 - - @backstage/plugin-kubernetes-backend@0.3.10 - - @backstage/plugin-rollbar-backend@0.1.13 - - example-app@0.2.37 - - @backstage/plugin-search-backend@0.2.3 - -## 0.2.36 - -### Patch Changes - -- Updated dependencies - - @backstage/integration@0.5.8 - - @backstage/plugin-scaffolder-backend@0.13.0 - - @backstage/catalog-model@0.9.0 - - @backstage/plugin-catalog-backend@0.12.0 - - @backstage/backend-common@0.8.5 - - @backstage/plugin-search-backend-node@0.3.0 - - example-app@0.2.36 - - @backstage/plugin-scaffolder-backend-module-rails@0.1.2 - - @backstage/catalog-client@0.3.16 - - @backstage/plugin-auth-backend@0.3.16 - - @backstage/plugin-badges-backend@0.1.8 - - @backstage/plugin-code-coverage-backend@0.1.8 - - @backstage/plugin-kafka-backend@0.2.8 - - @backstage/plugin-kubernetes-backend@0.3.9 - - @backstage/plugin-techdocs-backend@0.8.6 - - @backstage/plugin-todo-backend@0.1.8 - - @backstage/plugin-search-backend@0.2.2 - -## 0.2.35 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-scaffolder-backend@0.12.4 - - @backstage/backend-common@0.8.4 - - @backstage/plugin-auth-backend@0.3.15 - - @backstage/plugin-catalog-backend@0.11.0 - - @backstage/plugin-techdocs-backend@0.8.5 - - @backstage/catalog-client@0.3.15 - - @backstage/plugin-kafka-backend@0.2.7 - -## 0.2.32 - -### Patch Changes - -- Updated dependencies [9c63be545] -- Updated dependencies [92963779b] -- Updated dependencies [27a9b503a] -- Updated dependencies [66c6bfebd] -- Updated dependencies [55a253de2] -- Updated dependencies [70bc30c5b] -- Updated dependencies [db1c8f93b] -- Updated dependencies [5aff84759] -- Updated dependencies [f26e6008f] -- Updated dependencies [eda9dbd5f] -- Updated dependencies [4f8cf50fe] -- Updated dependencies [875809a59] - - @backstage/plugin-catalog-backend@0.10.2 - - @backstage/backend-common@0.8.2 - - @backstage/catalog-model@0.8.2 - - @backstage/plugin-scaffolder-backend@0.12.0 - - @backstage/catalog-client@0.3.13 - - @backstage/plugin-search-backend-node@0.2.0 - - @backstage/plugin-search-backend@0.2.0 - - @backstage/plugin-proxy-backend@0.2.9 - - example-app@0.2.32 - -## 0.2.30 - -### Patch Changes - -- Updated dependencies [0fd4ea443] -- Updated dependencies [add62a455] -- Updated dependencies [260aaa684] -- Updated dependencies [704875e26] - - @backstage/plugin-catalog-backend@0.10.0 - - @backstage/catalog-client@0.3.12 - - @backstage/catalog-model@0.8.0 - - @backstage/plugin-scaffolder-backend@0.11.4 - - example-app@0.2.30 - - @backstage/plugin-auth-backend@0.3.12 - - @backstage/plugin-badges-backend@0.1.6 - - @backstage/plugin-code-coverage-backend@0.1.6 - - @backstage/plugin-kafka-backend@0.2.6 - - @backstage/plugin-kubernetes-backend@0.3.8 - - @backstage/plugin-techdocs-backend@0.8.2 - - @backstage/plugin-todo-backend@0.1.6 - -## 0.2.28 - -### Patch Changes - -- Updated dependencies [062bbf90f] -- Updated dependencies [22fd8ce2a] -- Updated dependencies [10c008a3a] -- Updated dependencies [82ca1ac22] -- Updated dependencies [f9fb4a205] -- Updated dependencies [9a207f052] -- Updated dependencies [16be1d093] -- Updated dependencies [fd39d4662] -- Updated dependencies [f9f9d633d] - - @backstage/plugin-scaffolder-backend@0.11.1 - - @backstage/backend-common@0.8.0 - - @backstage/catalog-model@0.7.9 - - @backstage/plugin-catalog-backend@0.9.0 - - @backstage/plugin-kubernetes-backend@0.3.7 - - example-app@0.2.28 - - @backstage/plugin-app-backend@0.3.13 - - @backstage/plugin-auth-backend@0.3.10 - - @backstage/plugin-badges-backend@0.1.4 - - @backstage/plugin-code-coverage-backend@0.1.5 - - @backstage/plugin-graphql-backend@0.1.8 - - @backstage/plugin-kafka-backend@0.2.5 - - @backstage/plugin-proxy-backend@0.2.8 - - @backstage/plugin-rollbar-backend@0.1.11 - - @backstage/plugin-search-backend@0.1.5 - - @backstage/plugin-techdocs-backend@0.8.1 - - @backstage/plugin-todo-backend@0.1.5 - -## 0.2.27 - -### Patch Changes - -- Updated dependencies [e0bfd3d44] -- Updated dependencies [e0bfd3d44] -- Updated dependencies [e0bfd3d44] -- Updated dependencies [38ca05168] -- Updated dependencies [b219821a0] -- Updated dependencies [69eefb5ae] -- Updated dependencies [f53fba29f] -- Updated dependencies [75c8cec39] -- Updated dependencies [227439a72] -- Updated dependencies [cdb3426e5] -- Updated dependencies [d8b81fd28] -- Updated dependencies [d1b1306d9] - - @backstage/plugin-scaffolder-backend@0.11.0 - - @backstage/backend-common@0.7.0 - - @backstage/plugin-techdocs-backend@0.8.0 - - @backstage/plugin-catalog-backend@0.8.2 - - @backstage/plugin-kubernetes-backend@0.3.6 - - @backstage/plugin-proxy-backend@0.2.7 - - @backstage/catalog-model@0.7.8 - - @backstage/config@0.1.5 - - @backstage/catalog-client@0.3.11 - - example-app@0.2.27 - - @backstage/plugin-app-backend@0.3.12 - - @backstage/plugin-auth-backend@0.3.9 - - @backstage/plugin-badges-backend@0.1.3 - - @backstage/plugin-code-coverage-backend@0.1.4 - - @backstage/plugin-graphql-backend@0.1.7 - - @backstage/plugin-kafka-backend@0.2.4 - - @backstage/plugin-rollbar-backend@0.1.10 - - @backstage/plugin-search-backend@0.1.4 - - @backstage/plugin-todo-backend@0.1.4 - -## 0.2.25 - -### Patch Changes - -- Updated dependencies [b9b2b4b76] -- Updated dependencies [84c54474d] -- Updated dependencies [49574a8a3] -- Updated dependencies [d367f63b5] -- Updated dependencies [5fe62f124] -- Updated dependencies [09b5fcf2e] -- Updated dependencies [55b2fc0c0] -- Updated dependencies [c42cd1daa] -- Updated dependencies [b42531cfe] -- Updated dependencies [c2306f898] - - @backstage/plugin-search-backend@0.1.3 - - @backstage/plugin-search-backend-node@0.1.3 - - @backstage/plugin-scaffolder-backend@0.10.0 - - @backstage/plugin-rollbar-backend@0.1.9 - - @backstage/backend-common@0.6.3 - - @backstage/plugin-catalog-backend@0.8.0 - - @backstage/plugin-code-coverage-backend@0.1.2 - - @backstage/plugin-kubernetes-backend@0.3.5 - - example-app@0.2.25 - -## 0.2.22 - -### Patch Changes - -- Updated dependencies [f03a52f5b] -- Updated dependencies [676ede643] -- Updated dependencies [1ac6a5233] -- Updated dependencies [2ab6f3ff0] -- Updated dependencies [0d55dcc74] -- Updated dependencies [29e1789e1] -- Updated dependencies [f1b2c1d2c] -- Updated dependencies [60e463c8d] -- Updated dependencies [676ede643] -- Updated dependencies [b196a4569] -- Updated dependencies [8488a1a96] -- Updated dependencies [37e3a69f5] -- Updated dependencies [6b2d54fd6] -- Updated dependencies [44590510d] -- Updated dependencies [164cc4c53] - - @backstage/plugin-kafka-backend@0.2.3 - - @backstage/plugin-catalog-backend@0.7.0 - - @backstage/plugin-kubernetes-backend@0.3.3 - - @backstage/plugin-scaffolder-backend@0.9.4 - - @backstage/plugin-auth-backend@0.3.7 - - @backstage/catalog-client@0.3.9 - - @backstage/plugin-todo-backend@0.1.3 - - @backstage/catalog-model@0.7.5 - - @backstage/backend-common@0.6.1 - -## 0.2.21 - -### Patch Changes - -- Updated dependencies [a2a3c7803] -- Updated dependencies [9f2e51e89] -- Updated dependencies [4d248725e] -- Updated dependencies [aaeb7ecf3] -- Updated dependencies [449776cd6] -- Updated dependencies [91e87c055] -- Updated dependencies [36d933ec5] -- Updated dependencies [113d3d59e] -- Updated dependencies [f47e11427] -- Updated dependencies [c862b3f36] - - @backstage/plugin-kubernetes-backend@0.3.2 - - @backstage/plugin-scaffolder-backend@0.9.3 - - @backstage/plugin-search-backend@0.1.2 - - @backstage/plugin-search-backend-node@0.1.2 - - @backstage/plugin-techdocs-backend@0.7.0 - - @backstage/plugin-auth-backend@0.3.6 - - @backstage/plugin-todo-backend@0.1.2 - - @backstage/plugin-catalog-backend@0.6.7 - - example-app@0.2.21 - -## 0.2.20 - -### Patch Changes - -- Updated dependencies [010aed784] -- Updated dependencies [8686eb38c] -- Updated dependencies [e7baa0d2e] -- Updated dependencies [8b4f7e42a] -- Updated dependencies [8686eb38c] -- Updated dependencies [0434853a5] -- Updated dependencies [4bc98a5b9] -- Updated dependencies [d2f4efc5d] -- Updated dependencies [8686eb38c] -- Updated dependencies [424742dc1] -- Updated dependencies [1f98a6ff8] -- Updated dependencies [8b5e59750] -- Updated dependencies [8686eb38c] - - @backstage/plugin-catalog-backend@0.6.6 - - @backstage/catalog-client@0.3.8 - - @backstage/plugin-techdocs-backend@0.6.5 - - @backstage/plugin-scaffolder-backend@0.9.2 - - @backstage/backend-common@0.6.0 - - @backstage/config@0.1.4 - - @backstage/plugin-auth-backend@0.3.5 - - @backstage/plugin-kubernetes-backend@0.3.1 - - example-app@0.2.20 - - @backstage/plugin-app-backend@0.3.10 - - @backstage/plugin-graphql-backend@0.1.6 - - @backstage/plugin-kafka-backend@0.2.2 - - @backstage/plugin-proxy-backend@0.2.6 - - @backstage/plugin-rollbar-backend@0.1.8 - - @backstage/plugin-todo-backend@0.1.1 - -## 0.2.19 - -### Patch Changes - -- Updated dependencies [5d7834baf] -- Updated dependencies [9ef5a126d] -- Updated dependencies [d7245b733] -- Updated dependencies [393b623ae] -- Updated dependencies [d7245b733] -- Updated dependencies [0b42fff22] -- Updated dependencies [0b42fff22] -- Updated dependencies [2ef5bc7ea] -- Updated dependencies [c532c1682] -- Updated dependencies [761698831] -- Updated dependencies [aa095e469] -- Updated dependencies [761698831] -- Updated dependencies [f98f212e4] -- Updated dependencies [9581ff0b4] -- Updated dependencies [93c62c755] -- Updated dependencies [02d78290a] -- Updated dependencies [a501128db] -- Updated dependencies [8de9963f0] -- Updated dependencies [5f1b7ea35] -- Updated dependencies [2e57922de] -- Updated dependencies [e2c1b3fb6] - - @backstage/plugin-kubernetes-backend@0.3.0 - - @backstage/plugin-catalog-backend@0.6.5 - - @backstage/backend-common@0.5.6 - - @backstage/plugin-app-backend@0.3.9 - - @backstage/plugin-scaffolder-backend@0.9.1 - - @backstage/catalog-model@0.7.4 - - @backstage/catalog-client@0.3.7 - - @backstage/plugin-techdocs-backend@0.6.4 - - @backstage/plugin-auth-backend@0.3.4 - - example-app@0.2.19 - -## 0.2.18 - -### Patch Changes - -- Updated dependencies [12d8f27a6] -- Updated dependencies [52b5bc3e2] -- Updated dependencies [ecdd407b1] -- Updated dependencies [4fbc9df79] -- Updated dependencies [12d8f27a6] -- Updated dependencies [497859088] -- Updated dependencies [1987c9341] -- Updated dependencies [f31b76b44] -- Updated dependencies [15eee03bc] -- Updated dependencies [f43192207] -- Updated dependencies [8adb48df4] -- Updated dependencies [e3adec2bd] -- Updated dependencies [9ce68b677] -- Updated dependencies [8106c9528] -- Updated dependencies [d0ed25196] -- Updated dependencies [96ccc8f69] -- Updated dependencies [3af994c81] - - @backstage/plugin-scaffolder-backend@0.9.0 - - @backstage/plugin-techdocs-backend@0.6.3 - - @backstage/plugin-catalog-backend@0.6.4 - - @backstage/plugin-kafka-backend@0.2.1 - - @backstage/catalog-model@0.7.3 - - @backstage/backend-common@0.5.5 - - @backstage/plugin-proxy-backend@0.2.5 - - @backstage/plugin-auth-backend@0.3.3 - - @backstage/plugin-kubernetes-backend@0.2.8 - - example-app@0.2.18 - -## 0.2.17 - -### Patch Changes - -- Updated dependencies [a70af22a2] -- Updated dependencies [ec504e7b4] -- Updated dependencies [a5f42cf66] -- Updated dependencies [f37992797] -- Updated dependencies [bad21a085] -- Updated dependencies [1c06cb312] -- Updated dependencies [2499f6cde] -- Updated dependencies [a1f5e6545] - - @backstage/plugin-kubernetes-backend@0.2.7 - - @backstage/plugin-auth-backend@0.3.2 - - @backstage/plugin-scaffolder-backend@0.8.0 - - @backstage/plugin-techdocs-backend@0.6.2 - - @backstage/catalog-model@0.7.2 - - @backstage/plugin-app-backend@0.3.8 - - @backstage/plugin-catalog-backend@0.6.3 - - @backstage/config@0.1.3 - - example-app@0.2.17 - -## 0.2.15 - -### Patch Changes - -- Updated dependencies [1deb31141] -- Updated dependencies [6ed2b47d6] -- Updated dependencies [77ad0003a] -- Updated dependencies [d2441aee3] -- Updated dependencies [727f0deec] -- Updated dependencies [fb53eb7cb] -- Updated dependencies [07bafa248] -- Updated dependencies [ffffea8e6] -- Updated dependencies [f3fbfb452] -- Updated dependencies [615103a63] -- Updated dependencies [84364b35c] -- Updated dependencies [82b2c11b6] -- Updated dependencies [965e200c6] -- Updated dependencies [5a5163519] -- Updated dependencies [82b2c11b6] -- Updated dependencies [08142b256] -- Updated dependencies [08142b256] - - @backstage/plugin-auth-backend@0.3.0 - - @backstage/plugin-scaffolder-backend@0.7.0 - - @backstage/plugin-catalog-backend@0.6.1 - - @backstage/plugin-app-backend@0.3.7 - - example-app@0.2.15 - - @backstage/backend-common@0.5.3 - - @backstage/plugin-techdocs-backend@0.6.0 - -## 0.2.14 - -### Patch Changes - -- Updated dependencies [c777df180] -- Updated dependencies [2430ee7c2] -- Updated dependencies [3149bfe63] -- Updated dependencies [6e612ce25] -- Updated dependencies [e44925723] -- Updated dependencies [9d6ef14bc] -- Updated dependencies [a26668913] -- Updated dependencies [025e122c3] -- Updated dependencies [e9aab60c7] -- Updated dependencies [24e47ef1e] -- Updated dependencies [7881f2117] -- Updated dependencies [529d16d27] -- Updated dependencies [cdea0baf1] -- Updated dependencies [11cb5ef94] - - @backstage/plugin-techdocs-backend@0.5.5 - - @backstage/backend-common@0.5.2 - - @backstage/plugin-catalog-backend@0.6.0 - - @backstage/catalog-model@0.7.1 - - example-app@0.2.14 - - @backstage/plugin-scaffolder-backend@0.6.0 - - @backstage/plugin-app-backend@0.3.6 - -## 0.2.13 - -### Patch Changes - -- Updated dependencies [26a3a6cf0] -- Updated dependencies [681111228] -- Updated dependencies [664dd08c9] -- Updated dependencies [9dd057662] -- Updated dependencies [234e7d985] -- Updated dependencies [d7b1d317f] -- Updated dependencies [a91aa6bf2] -- Updated dependencies [39b05b9ae] -- Updated dependencies [4eaa06057] - - @backstage/backend-common@0.5.1 - - @backstage/plugin-scaffolder-backend@0.5.2 - - @backstage/plugin-kubernetes-backend@0.2.6 - - @backstage/plugin-catalog-backend@0.5.5 - - @backstage/plugin-kafka-backend@0.2.0 - - @backstage/plugin-auth-backend@0.2.12 - - example-app@0.2.13 - - @backstage/plugin-app-backend@0.3.5 - -## 0.2.12 - -### Patch Changes - -- Updated dependencies [def2307f3] -- Updated dependencies [d54857099] -- Updated dependencies [0b135e7e0] -- Updated dependencies [318a6af9f] -- Updated dependencies [294a70cab] -- Updated dependencies [ac7be581a] -- Updated dependencies [0ea032763] -- Updated dependencies [5345a1f98] -- Updated dependencies [ed6baab66] -- Updated dependencies [ad838c02f] -- Updated dependencies [a5e27d5c1] -- Updated dependencies [0643a3336] -- Updated dependencies [a2291d7cc] -- Updated dependencies [f9ba00a1c] -- Updated dependencies [09a370426] -- Updated dependencies [a93f42213] - - @backstage/catalog-model@0.7.0 - - @backstage/plugin-catalog-backend@0.5.4 - - @backstage/plugin-kubernetes-backend@0.2.5 - - @backstage/backend-common@0.5.0 - - @backstage/plugin-scaffolder-backend@0.5.0 - - @backstage/plugin-techdocs-backend@0.5.4 - - @backstage/plugin-auth-backend@0.2.11 - - example-app@0.2.12 - - @backstage/plugin-kafka-backend@0.1.1 - - @backstage/plugin-app-backend@0.3.4 - - @backstage/plugin-graphql-backend@0.1.5 - - @backstage/plugin-proxy-backend@0.2.4 - - @backstage/plugin-rollbar-backend@0.1.7 - -## 0.2.11 - -### Patch Changes - -- cc068c0d6: Bump the gitbeaker dependencies to 28.x. - - To update your own installation, go through the `package.json` files of all of - your packages, and ensure that all dependencies on `@gitbeaker/node` or - `@gitbeaker/core` are at version `^28.0.2`. Then run `yarn install` at the root - of your repo. - -- Updated dependencies [68ad5af51] -- Updated dependencies [5a9a7e7c2] -- Updated dependencies [f3b064e1c] -- Updated dependencies [94fdf4955] -- Updated dependencies [cc068c0d6] -- Updated dependencies [ade6b3bdf] -- Updated dependencies [468579734] -- Updated dependencies [cb7af51e7] -- Updated dependencies [abbee6fff] -- Updated dependencies [147fadcb9] -- Updated dependencies [711ba55a2] - - @backstage/plugin-techdocs-backend@0.5.3 - - @backstage/plugin-kubernetes-backend@0.2.4 - - @backstage/catalog-model@0.6.1 - - @backstage/plugin-catalog-backend@0.5.3 - - @backstage/plugin-scaffolder-backend@0.4.1 - - @backstage/plugin-auth-backend@0.2.10 - - @backstage/backend-common@0.4.3 - -## 0.2.10 - -### Patch Changes - -- Updated dependencies [5eb8c9b9e] -- Updated dependencies [7e3451700] - - @backstage/plugin-scaffolder-backend@0.4.0 - -## 0.2.8 - -### Patch Changes - -- 7cfcd58ee: use node 14 for backend Dockerfile -- Updated dependencies [19554f6d6] -- Updated dependencies [33a82a713] -- Updated dependencies [5de26b9a6] -- Updated dependencies [30d6c78fb] -- Updated dependencies [5084e5039] -- Updated dependencies [a8573e53b] -- Updated dependencies [aed8f7f12] - - @backstage/plugin-scaffolder-backend@0.3.6 - - @backstage/plugin-catalog-backend@0.5.1 - - @backstage/plugin-techdocs-backend@0.5.0 - - example-app@0.2.8 - -## 0.2.7 - -### Patch Changes - -- Updated dependencies [c6eeefa35] -- Updated dependencies [fb386b760] -- Updated dependencies [c911061b7] -- Updated dependencies [7c3ffc0cd] -- Updated dependencies [dae4f3983] -- Updated dependencies [7b15cc271] -- Updated dependencies [e7496dc3e] -- Updated dependencies [1d1c2860f] -- Updated dependencies [0e6298f7e] -- Updated dependencies [8dd0a906d] -- Updated dependencies [4eafdec4a] -- Updated dependencies [6b37c95bf] -- Updated dependencies [8c31c681c] -- Updated dependencies [7b98e7fee] -- Updated dependencies [ac3560b42] -- Updated dependencies [94c65a9d4] -- Updated dependencies [0097057ed] - - @backstage/plugin-catalog-backend@0.5.0 - - @backstage/catalog-model@0.6.0 - - @backstage/plugin-techdocs-backend@0.4.0 - - @backstage/plugin-auth-backend@0.2.7 - - @backstage/backend-common@0.4.1 - - @backstage/plugin-scaffolder-backend@0.3.5 - - example-app@0.2.7 - - @backstage/plugin-kubernetes-backend@0.2.3 - -## 0.2.6 - -### Patch Changes - -- 1e22f8e0b: Unify `dockerode` library and type dependency versions -- Updated dependencies [6e8bb3ac0] -- Updated dependencies [e708679d7] -- Updated dependencies [047c018c9] -- Updated dependencies [38e24db00] -- Updated dependencies [e3bd9fc2f] -- Updated dependencies [12bbd748c] -- Updated dependencies [38d63fbe1] -- Updated dependencies [1e22f8e0b] -- Updated dependencies [83b6e0c1f] -- Updated dependencies [e3bd9fc2f] - - @backstage/plugin-catalog-backend@0.4.0 - - @backstage/backend-common@0.4.0 - - @backstage/config@0.1.2 - - @backstage/plugin-scaffolder-backend@0.3.4 - - @backstage/plugin-techdocs-backend@0.3.2 - - @backstage/catalog-model@0.5.0 - - example-app@0.2.6 - - @backstage/plugin-app-backend@0.3.3 - - @backstage/plugin-auth-backend@0.2.6 - - @backstage/plugin-graphql-backend@0.1.4 - - @backstage/plugin-kubernetes-backend@0.2.2 - - @backstage/plugin-proxy-backend@0.2.3 - - @backstage/plugin-rollbar-backend@0.1.5 - -## 0.2.5 - -### Patch Changes - -- Updated dependencies [ae95c7ff3] -- Updated dependencies [b4488ddb0] -- Updated dependencies [612368274] -- Updated dependencies [6a6c7c14e] -- Updated dependencies [08835a61d] -- Updated dependencies [a9fd599f7] -- Updated dependencies [e42402b47] -- Updated dependencies [bcc211a08] -- Updated dependencies [3619ea4c4] - - @backstage/plugin-techdocs-backend@0.3.1 - - @backstage/plugin-catalog-backend@0.3.0 - - @backstage/backend-common@0.3.3 - - @backstage/plugin-proxy-backend@0.2.2 - - @backstage/catalog-model@0.4.0 - - @backstage/plugin-kubernetes-backend@0.2.1 - - @backstage/plugin-app-backend@0.3.2 - - example-app@0.2.5 - - @backstage/plugin-auth-backend@0.2.5 - - @backstage/plugin-scaffolder-backend@0.3.3 - -## 0.2.4 - -### Patch Changes - -- Updated dependencies [50eff1d00] -- Updated dependencies [ff1301d28] -- Updated dependencies [4b53294a6] -- Updated dependencies [3aa7efb3f] -- Updated dependencies [1ec19a3f4] -- Updated dependencies [ab94c9542] -- Updated dependencies [3a201c5d5] -- Updated dependencies [2daf18e80] -- Updated dependencies [069cda35f] -- Updated dependencies [b3d4e4e57] -- Updated dependencies [700a212b4] - - @backstage/plugin-auth-backend@0.2.4 - - @backstage/plugin-app-backend@0.3.1 - - @backstage/plugin-techdocs-backend@0.3.0 - - @backstage/backend-common@0.3.2 - - @backstage/plugin-catalog-backend@0.2.3 - - @backstage/catalog-model@0.3.1 - - @backstage/plugin-rollbar-backend@0.1.4 - - example-app@0.2.4 - -## 0.2.3 - -### Patch Changes - -- Updated dependencies [1166fcc36] -- Updated dependencies [bff3305aa] -- Updated dependencies [0c2121240] -- Updated dependencies [ef2831dde] -- Updated dependencies [1185919f3] -- Updated dependencies [475fc0aaa] -- Updated dependencies [b47dce06f] -- Updated dependencies [5a1d8dca3] - - @backstage/catalog-model@0.3.0 - - @backstage/plugin-kubernetes-backend@0.2.0 - - @backstage/backend-common@0.3.1 - - @backstage/plugin-catalog-backend@0.2.2 - - @backstage/plugin-scaffolder-backend@0.3.2 - - example-app@0.2.3 - - @backstage/plugin-auth-backend@0.2.3 - - @backstage/plugin-techdocs-backend@0.2.2 - -## 0.2.2 - -### Patch Changes - -- Updated dependencies [1722cb53c] -- Updated dependencies [1722cb53c] -- Updated dependencies [1722cb53c] -- Updated dependencies [f531d307c] -- Updated dependencies [3efd03c0e] -- Updated dependencies [7b37e6834] -- Updated dependencies [8e2effb53] -- Updated dependencies [d33f5157c] - - @backstage/backend-common@0.3.0 - - @backstage/plugin-app-backend@0.3.0 - - @backstage/plugin-catalog-backend@0.2.1 - - example-app@0.2.2 - - @backstage/plugin-scaffolder-backend@0.3.1 - - @backstage/plugin-auth-backend@0.2.2 - - @backstage/plugin-graphql-backend@0.1.3 - - @backstage/plugin-kubernetes-backend@0.1.3 - - @backstage/plugin-proxy-backend@0.2.1 - - @backstage/plugin-rollbar-backend@0.1.3 - - @backstage/plugin-sentry-backend@0.1.3 - - @backstage/plugin-techdocs-backend@0.2.1 - -## 0.2.1 - -### Patch Changes - -- Updated dependencies [752808090] -- Updated dependencies [462876399] -- Updated dependencies [59166e5ec] -- Updated dependencies [33b7300eb] - - @backstage/plugin-auth-backend@0.2.1 - - @backstage/plugin-scaffolder-backend@0.3.0 - - @backstage/backend-common@0.2.1 - - example-app@0.2.1 - -## 0.2.0 - -### Patch Changes - -- 440a17b39: Bump @backstage/catalog-backend and pass the now required UrlReader interface to the plugin -- 6840a68df: Pass GitHub token into Scaffolder GitHub Preparer -- 8c2b76e45: **BREAKING CHANGE** - - The existing loading of additional config files like `app-config.development.yaml` using APP_ENV or NODE_ENV has been removed. - Instead, the CLI and backend process now accept one or more `--config` flags to load config files. - - Without passing any flags, `app-config.yaml` and, if it exists, `app-config.local.yaml` will be loaded. - If passing any `--config ` flags, only those files will be loaded, **NOT** the default `app-config.yaml` one. - - The old behaviour of for example `APP_ENV=development` can be replicated using the following flags: - - ```bash - --config ../../app-config.yaml --config ../../app-config.development.yaml - ``` - -- 7bbeb049f: Change loadBackendConfig to return the config directly -- Updated dependencies [28edd7d29] -- Updated dependencies [819a70229] -- Updated dependencies [3a4236570] -- Updated dependencies [3e254503d] -- Updated dependencies [6d29605db] -- Updated dependencies [e0be86b6f] -- Updated dependencies [f70a52868] -- Updated dependencies [12b5fe940] -- Updated dependencies [5249594c5] -- Updated dependencies [56e4eb589] -- Updated dependencies [b4e5466e1] -- Updated dependencies [6f1768c0f] -- Updated dependencies [e37c0a005] -- Updated dependencies [3472c8be7] -- Updated dependencies [57d555eb2] -- Updated dependencies [61db1ddc6] -- Updated dependencies [81cb94379] -- Updated dependencies [1687b8fbb] -- Updated dependencies [a768a07fb] -- Updated dependencies [a768a07fb] -- Updated dependencies [f00ca3cb8] -- Updated dependencies [0c370c979] -- Updated dependencies [ce1f55398] -- Updated dependencies [e6b00e3af] -- Updated dependencies [9226c2aaa] -- Updated dependencies [6d97d2d6f] -- Updated dependencies [99710b102] -- Updated dependencies [6579769df] -- Updated dependencies [002860e7a] -- Updated dependencies [5adfc005e] -- Updated dependencies [33454c0f2] -- Updated dependencies [183e2a30d] -- Updated dependencies [948052cbb] -- Updated dependencies [65d722455] -- Updated dependencies [b652bf2cc] -- Updated dependencies [4036ff59d] -- Updated dependencies [991a950e0] -- Updated dependencies [512d70973] -- Updated dependencies [8c2b76e45] -- Updated dependencies [8bdf0bcf5] -- Updated dependencies [c926765a2] -- Updated dependencies [5a920c6e4] -- Updated dependencies [2f62e1804] -- Updated dependencies [440a17b39] -- Updated dependencies [fa56f4615] -- Updated dependencies [8afce088a] -- Updated dependencies [4c4eab81b] -- Updated dependencies [22ff8fba5] -- Updated dependencies [36a71d278] -- Updated dependencies [b3d57961c] -- Updated dependencies [6840a68df] -- Updated dependencies [a5cb46bac] -- Updated dependencies [49d70ccab] -- Updated dependencies [1c8c43756] -- Updated dependencies [26e69ab1a] -- Updated dependencies [5e4551e3a] -- Updated dependencies [e142a2767] -- Updated dependencies [e7f5471fd] -- Updated dependencies [e3d063ffa] -- Updated dependencies [440a17b39] -- Updated dependencies [7bbeb049f] - - @backstage/plugin-app-backend@0.2.0 - - @backstage/plugin-auth-backend@0.2.0 - - @backstage/catalog-model@0.2.0 - - @backstage/plugin-scaffolder-backend@0.2.0 - - @backstage/plugin-techdocs-backend@0.2.0 - - @backstage/plugin-catalog-backend@0.2.0 - - @backstage/plugin-proxy-backend@0.2.0 - - @backstage/backend-common@0.2.0 - - example-app@0.2.0 - - @backstage/plugin-graphql-backend@0.1.2 - - @backstage/plugin-kubernetes-backend@0.1.2 - - @backstage/plugin-rollbar-backend@0.1.2 - - @backstage/plugin-sentry-backend@0.1.2 + - @backstage/backend-app-api@0.1.0-next.0 diff --git a/packages/backend/README.md b/packages/backend/README.md index f71fc9f9d8..46c749fc4b 100644 --- a/packages/backend/README.md +++ b/packages/backend/README.md @@ -1,6 +1,6 @@ # example-backend -This package is an EXAMPLE of a Backstage backend. +This package is an EXAMPLE of a Backstage backend using the [new backend system](https://backstage.io/docs/backend-system/). The main purpose of this package is to provide a test bed for Backstage plugins that have a backend part. Feel free to experiment locally or within your fork @@ -16,22 +16,22 @@ To run the example backend, first go to the project root and run yarn install ``` -You should only need to do this once. +This will install all dependencies for the project. You only need to do this once, unless you make changes to the dependency definitions. -After that, go to the `packages/backend` directory and run +You can then start the backend by running the following command in the repo root: ```bash -yarn start +yarn start-backend ``` If you want to override any configuration locally, for example adding any secrets, -you can do so in `app-config.local.yaml`. +you can do so in `app-config.local.yaml`, next to `app-config.yaml`. The backend starts up on port 7007 per default. ### Debugging -The backend is a node process that can be inspected to allow breakpoints and live debugging. To enable this, pass the `--inspect` flag to [backend:dev](https://backstage.io/docs/local-dev/cli-build-system#backend-development). +The backend is a node process that can be inspected to allow breakpoints and live debugging. To enable this, pass the `--inspect` flag when starting the backend. To debug the backend in [Visual Studio Code](https://code.visualstudio.com/): @@ -51,9 +51,7 @@ in `app-config.yaml` under `catalog.locations`. For local development you can ov ## Authentication -We chose [Passport](http://www.passportjs.org/) as authentication platform due to its comprehensive set of supported authentication [strategies](http://www.passportjs.org/packages/). - -Read more about the [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) and [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md) +The example backend has guest access enabled by default. This means you do not need to configure a real authentication provider, but will instead be logged in as a guest user. ## Documentation diff --git a/packages/backend/knip-report.md b/packages/backend/knip-report.md index f57bda79b7..a26b412ee9 100644 --- a/packages/backend/knip-report.md +++ b/packages/backend/knip-report.md @@ -1,27 +1,12 @@ # Knip report -## Unused dependencies (13) +## Unused dependencies (5) -| Name | Location | Severity | -| :------------------------------------------------- | :----------- | :------- | -| @backstage/plugin-scaffolder-backend-module-gitlab | package.json | error | -| @backstage/plugin-scaffolder-backend-module-rails | package.json | error | -| @backstage/plugin-azure-sites-common | package.json | error | -| @backstage/plugin-tech-insights-node | package.json | error | -| azure-devops-node-api | package.json | error | -| pg-connection-string | package.json | error | -| @gitbeaker/node | package.json | error | -| better-sqlite3 | package.json | error | -| @octokit/rest | package.json | error | -| example-app | package.json | error | -| mysql2 | package.json | error | -| luxon | package.json | error | -| pg | package.json | error | - -## Unused devDependencies (2) - -| Name | Location | Severity | -| :------------------------------- | :----------- | :------- | -| @types/express-serve-static-core | package.json | error | -| @types/luxon | package.json | error | +| Name | Location | Severity | +| :----------------------------------------------- | :----------- | :------- | +| @backstage/plugin-catalog-backend-module-openapi | package.json | error | +| @backstage/plugin-search-backend-node | package.json | error | +| @backstage/plugin-permission-common | package.json | error | +| @backstage/plugin-permission-node | package.json | error | +| @backstage/backend-tasks | package.json | error | diff --git a/packages/backend/package.json b/packages/backend/package.json index 7f42df6681..c76a3be0a3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.98-next.1", + "version": "0.0.26-next.1", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -18,75 +18,48 @@ "backstage" ], "scripts": { - "start": "backstage-cli package start", "build": "backstage-cli package build", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "start": "backstage-cli package start", + "test": "backstage-cli package test", "build-image": "docker build ../.. -f Dockerfile --tag example-backend" }, "dependencies": { - "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", - "@backstage/integration": "workspace:^", "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", + "@backstage/plugin-auth-backend-module-github-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^", + "@backstage/plugin-catalog-backend-module-openapi": "workspace:^", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^", - "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-devtools-backend": "workspace:^", - "@backstage/plugin-events-backend": "workspace:^", - "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", + "@backstage/plugin-notifications-backend": "workspace:^", "@backstage/plugin-permission-backend": "workspace:^", + "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "@backstage/plugin-proxy-backend": "workspace:^", "@backstage/plugin-scaffolder-backend": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^", - "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-github": "workspace:^", "@backstage/plugin-search-backend": "workspace:^", "@backstage/plugin-search-backend-module-catalog": "workspace:^", - "@backstage/plugin-search-backend-module-elasticsearch": "workspace:^", "@backstage/plugin-search-backend-module-explore": "workspace:^", - "@backstage/plugin-search-backend-module-pg": "workspace:^", "@backstage/plugin-search-backend-module-techdocs": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", - "@backstage/plugin-signals-node": "workspace:^", - "@backstage/plugin-techdocs-backend": "workspace:^", - "@gitbeaker/node": "^35.1.0", - "@octokit/rest": "^19.0.3", - "@opentelemetry/api": "^1.4.1", - "@opentelemetry/exporter-prometheus": "^0.50.0", - "@opentelemetry/sdk-metrics": "^1.13.0", - "azure-devops-node-api": "^12.0.0", - "better-sqlite3": "^9.0.0", - "dockerode": "^4.0.0", - "example-app": "link:../app", - "express": "^4.17.1", - "express-prom-bundle": "^7.0.0", - "express-promise-router": "^4.1.0", - "luxon": "^3.0.0", - "mysql2": "^3.0.0", - "pg": "^8.11.3", - "pg-connection-string": "^2.3.0", - "prom-client": "^15.0.0", - "winston": "^3.2.1" + "@backstage/plugin-techdocs-backend": "workspace:^" }, "devDependencies": { - "@backstage/cli": "workspace:^", - "@types/dockerode": "^3.3.0", - "@types/express": "^4.17.6", - "@types/express-serve-static-core": "^4.17.5", - "@types/luxon": "^3.0.0" + "@backstage/cli": "workspace:^" }, "files": [ "dist" diff --git a/packages/backend-next/src/authModuleGithubProvider.ts b/packages/backend/src/authModuleGithubProvider.ts similarity index 100% rename from packages/backend-next/src/authModuleGithubProvider.ts rename to packages/backend/src/authModuleGithubProvider.ts diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 34ea8b0f6c..a4acd19cf2 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -14,171 +14,38 @@ * limitations under the License. */ -/* - * Hi! - * - * Note that this is an EXAMPLE Backstage backend. Please check the README. - * - * Happy hacking! - */ +import { createBackend } from '@backstage/backend-defaults'; -import Router from 'express-promise-router'; -import { - CacheManager, - createServiceBuilder, - DatabaseManager, - getRootLogger, - HostDiscovery, - loadBackendConfig, - notFoundHandler, - ServerTokenManager, - UrlReaders, - useHotMemoize, -} from '@backstage/backend-common'; -import { TaskScheduler } from '@backstage/backend-tasks'; -import { Config } from '@backstage/config'; -import healthcheck from './plugins/healthcheck'; -import { metricsHandler, metricsInit } from './metrics'; -import auth from './plugins/auth'; -import catalog from './plugins/catalog'; -import events from './plugins/events'; -import kubernetes from './plugins/kubernetes'; -import scaffolder from './plugins/scaffolder'; -import proxy from './plugins/proxy'; -import search from './plugins/search'; -import techdocs from './plugins/techdocs'; -import app from './plugins/app'; -import permission from './plugins/permission'; -import signals from './plugins/signals'; -import devtools from './plugins/devtools'; -import { PluginEnvironment } from './types'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { DefaultEventBroker } from '@backstage/plugin-events-backend'; -import { DefaultEventsService } from '@backstage/plugin-events-node'; -import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; -import { MeterProvider } from '@opentelemetry/sdk-metrics'; -import { metrics } from '@opentelemetry/api'; -import { DefaultSignalsService } from '@backstage/plugin-signals-node'; +const backend = createBackend(); -// Expose opentelemetry metrics using a Prometheus exporter on -// http://localhost:9464/metrics . See prometheus.yml in packages/backend for -// more information on how to scrape it. -const exporter = new PrometheusExporter(); -const meterProvider = new MeterProvider(); -metrics.setGlobalMeterProvider(meterProvider); -meterProvider.addMetricReader(exporter); +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('./authModuleGithubProvider')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); -function makeCreateEnv(config: Config) { - const root = getRootLogger(); - const reader = UrlReaders.default({ logger: root, config }); - const discovery = HostDiscovery.fromConfig(config); - const tokenManager = ServerTokenManager.fromConfig(config, { logger: root }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); - const cacheManager = CacheManager.fromConfig(config); - const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); - const identity = DefaultIdentityClient.create({ - discovery, - }); +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed')); +backend.add( + import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), +); +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +backend.add(import('@backstage/plugin-devtools-backend')); +backend.add(import('@backstage/plugin-kubernetes-backend/alpha')); +backend.add( + import('@backstage/plugin-permission-backend-module-allow-all-policy'), +); +backend.add(import('@backstage/plugin-permission-backend/alpha')); +backend.add(import('@backstage/plugin-proxy-backend/alpha')); +backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); +backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); +backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); +backend.add(import('@backstage/plugin-search-backend-module-explore/alpha')); +backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); +backend.add( + import('@backstage/plugin-catalog-backend-module-backstage-openapi'), +); +backend.add(import('@backstage/plugin-search-backend/alpha')); +backend.add(import('@backstage/plugin-techdocs-backend/alpha')); +backend.add(import('@backstage/plugin-signals-backend')); +backend.add(import('@backstage/plugin-notifications-backend')); - const eventsService = DefaultEventsService.create({ logger: root }); - const eventBroker = new DefaultEventBroker( - root.child({ type: 'plugin' }), - eventsService, - ); - const signalsService = DefaultSignalsService.create({ - events: eventsService, - }); - - root.info(`Created UrlReader ${reader}`); - - return (plugin: string): PluginEnvironment => { - const logger = root.child({ type: 'plugin', plugin }); - const database = databaseManager.forPlugin(plugin); - const cache = cacheManager.forPlugin(plugin); - const scheduler = taskScheduler.forPlugin(plugin); - - return { - logger, - cache, - database, - config, - reader, - eventBroker, - events: eventsService, - discovery, - tokenManager, - permissions, - scheduler, - identity, - signals: signalsService, - }; - }; -} - -async function main() { - metricsInit(); - const logger = getRootLogger(); - - logger.info( - `You are running an example backend, which is supposed to be mainly used for contributing back to Backstage. ` + - `Do NOT deploy this to production. Read more here https://backstage.io/docs/getting-started/`, - ); - - const config = await loadBackendConfig({ - argv: process.argv, - logger, - }); - - const createEnv = makeCreateEnv(config); - - const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck')); - const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); - const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); - const authEnv = useHotMemoize(module, () => createEnv('auth')); - const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); - const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - const permissionEnv = useHotMemoize(module, () => createEnv('permission')); - const eventsEnv = useHotMemoize(module, () => createEnv('events')); - const devToolsEnv = useHotMemoize(module, () => createEnv('devtools')); - const signalsEnv = useHotMemoize(module, () => createEnv('signals')); - - const apiRouter = Router(); - apiRouter.use('/catalog', await catalog(catalogEnv)); - apiRouter.use('/events', await events(eventsEnv)); - apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv)); - apiRouter.use('/auth', await auth(authEnv)); - apiRouter.use('/search', await search(searchEnv)); - apiRouter.use('/techdocs', await techdocs(techdocsEnv)); - apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv)); - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/permission', await permission(permissionEnv)); - apiRouter.use('/devtools', await devtools(devToolsEnv)); - apiRouter.use('/signals', await signals(signalsEnv)); - apiRouter.use(notFoundHandler()); - - const service = createServiceBuilder(module) - .loadConfig(config) - .addRouter('', await healthcheck(healthcheckEnv)) - .addRouter('', metricsHandler()) - .addRouter('/api', apiRouter) - .addRouter('', await app(appEnv)); - - await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); -main().catch(error => { - console.error('Backend failed to start up', error); - process.exit(1); -}); +backend.start(); diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index d15399d279..edd462b528 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -14,7 +14,7 @@ import { AwsAlbResult as AwsAlbResult_2 } from '@backstage/plugin-auth-backend-m import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageSignInResult } from '@backstage/plugin-auth-node'; -import { CacheService } from '@backstage/backend-plugin-api'; +import { CacheClient } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { ClientAuthResponse } from '@backstage/plugin-auth-node'; import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; @@ -452,7 +452,7 @@ export const providers: Readonly<{ signIn: { resolver: SignInResolver_2; }; - cache?: CacheService | undefined; + cache?: CacheClient | undefined; }) => AuthProviderFactory_2; resolvers: Readonly; }>; diff --git a/plugins/catalog-backend-module-unprocessed/README.md b/plugins/catalog-backend-module-unprocessed/README.md index 0d9f9f8cb9..6b350e5ef8 100644 --- a/plugins/catalog-backend-module-unprocessed/README.md +++ b/plugins/catalog-backend-module-unprocessed/README.md @@ -14,7 +14,13 @@ A `pending` entity has not been processed yet. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-unprocessed ``` -### backend +In `packages/backend/src/index.ts` add the module: + +```ts title="packages/backend/src/index.ts" +backend.add(catalogModuleUnprocessedEntities()); +``` + +### Legacy Backend In `packages/backend/src/plugins/catalog.ts` import the module and initialize it after invoking `CatalogBuilder.build()`: @@ -29,13 +35,3 @@ const unprocessed = new UnprocessedEntitiesModule( ); unprocessed.registerRoutes(); ``` - -### backend-next - -In `packages/backend-next/src/index.ts` add the module: - -```ts title="packages/backend-next/src/index.ts" -backend.add(catalogModuleUnprocessedEntities()); -``` - -_This plugin was created through the Backstage CLI_ diff --git a/plugins/search-backend-module-catalog/README.md b/plugins/search-backend-module-catalog/README.md index cbabe13d6d..925d8d3b09 100644 --- a/plugins/search-backend-module-catalog/README.md +++ b/plugins/search-backend-module-catalog/README.md @@ -14,7 +14,7 @@ yarn --cwd packages/backend add @backstage/plugin-search-backend-module-catalog Add the collator to your backend instance, along with the search plugin itself: ```tsx -// packages/backend-next/src/index.ts +// packages/backend/src/index.ts import { createBackend } from '@backstage/backend-defaults'; import { searchPlugin } from '@backstage/plugin-search-backend/alpha'; import { searchModuleCatalogCollator } from '@backstage/plugin-search-backend-module-catalog/alpha'; @@ -32,7 +32,7 @@ You may also want to add configuration parameters to your app-config, for exampl This module also has an extension point, which lets you inject advanced customizations. Here's an example of how to leverage that extension point to tweak the transformer used for building the search indexer documents: ```tsx -// packages/backend-next/src/index.ts +// packages/backend/src/index.ts import { createBackend } from '@backstage/backend-defaults'; import { createBackendModule } from '@backstage/backend-plugin-api'; import { searchPlugin } from '@backstage/plugin-search-backend/alpha'; diff --git a/yarn.lock b/yarn.lock index f90b972ae7..48db161383 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6695,7 +6695,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-scaffolder-backend-module-notifications@workspace:^, @backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications": +"@backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications": version: 0.0.0-use.local resolution: "@backstage/plugin-scaffolder-backend-module-notifications@workspace:plugins/scaffolder-backend-module-notifications" dependencies: @@ -24107,9 +24107,9 @@ __metadata: languageName: unknown linkType: soft -"example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend": +"example-app@link:../app::locator=example-backend-legacy%40workspace%3Apackages%2Fbackend-legacy": version: 0.0.0-use.local - resolution: "example-app@link:../app::locator=example-backend%40workspace%3Apackages%2Fbackend" + resolution: "example-app@link:../app::locator=example-backend-legacy%40workspace%3Apackages%2Fbackend-legacy" languageName: node linkType: soft @@ -24181,48 +24181,9 @@ __metadata: languageName: unknown linkType: soft -"example-backend-next@workspace:packages/backend-next": +"example-backend-legacy@workspace:packages/backend-legacy": version: 0.0.0-use.local - resolution: "example-backend-next@workspace:packages/backend-next" - dependencies: - "@backstage/backend-defaults": "workspace:^" - "@backstage/backend-plugin-api": "workspace:^" - "@backstage/backend-tasks": "workspace:^" - "@backstage/catalog-model": "workspace:^" - "@backstage/cli": "workspace:^" - "@backstage/plugin-app-backend": "workspace:^" - "@backstage/plugin-auth-backend": "workspace:^" - "@backstage/plugin-auth-backend-module-github-provider": "workspace:^" - "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" - "@backstage/plugin-auth-node": "workspace:^" - "@backstage/plugin-catalog-backend": "workspace:^" - "@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^" - "@backstage/plugin-catalog-backend-module-openapi": "workspace:^" - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^" - "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^" - "@backstage/plugin-devtools-backend": "workspace:^" - "@backstage/plugin-kubernetes-backend": "workspace:^" - "@backstage/plugin-notifications-backend": "workspace:^" - "@backstage/plugin-permission-backend": "workspace:^" - "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^" - "@backstage/plugin-permission-common": "workspace:^" - "@backstage/plugin-permission-node": "workspace:^" - "@backstage/plugin-proxy-backend": "workspace:^" - "@backstage/plugin-scaffolder-backend": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-github": "workspace:^" - "@backstage/plugin-search-backend": "workspace:^" - "@backstage/plugin-search-backend-module-catalog": "workspace:^" - "@backstage/plugin-search-backend-module-explore": "workspace:^" - "@backstage/plugin-search-backend-module-techdocs": "workspace:^" - "@backstage/plugin-search-backend-node": "workspace:^" - "@backstage/plugin-signals-backend": "workspace:^" - "@backstage/plugin-techdocs-backend": "workspace:^" - languageName: unknown - linkType: soft - -"example-backend@workspace:packages/backend": - version: 0.0.0-use.local - resolution: "example-backend@workspace:packages/backend" + resolution: "example-backend-legacy@workspace:packages/backend-legacy" dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-tasks": "workspace:^" @@ -24249,7 +24210,6 @@ __metadata: "@backstage/plugin-scaffolder-backend": "workspace:^" "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^" "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^" - "@backstage/plugin-scaffolder-backend-module-notifications": "workspace:^" "@backstage/plugin-scaffolder-backend-module-rails": "workspace:^" "@backstage/plugin-search-backend": "workspace:^" "@backstage/plugin-search-backend-module-catalog": "workspace:^" @@ -24286,6 +24246,45 @@ __metadata: languageName: unknown linkType: soft +"example-backend@workspace:packages/backend": + version: 0.0.0-use.local + resolution: "example-backend@workspace:packages/backend" + dependencies: + "@backstage/backend-defaults": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-tasks": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/plugin-app-backend": "workspace:^" + "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-backend-module-github-provider": "workspace:^" + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-backend-module-backstage-openapi": "workspace:^" + "@backstage/plugin-catalog-backend-module-openapi": "workspace:^" + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^" + "@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^" + "@backstage/plugin-devtools-backend": "workspace:^" + "@backstage/plugin-kubernetes-backend": "workspace:^" + "@backstage/plugin-notifications-backend": "workspace:^" + "@backstage/plugin-permission-backend": "workspace:^" + "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^" + "@backstage/plugin-permission-common": "workspace:^" + "@backstage/plugin-permission-node": "workspace:^" + "@backstage/plugin-proxy-backend": "workspace:^" + "@backstage/plugin-scaffolder-backend": "workspace:^" + "@backstage/plugin-scaffolder-backend-module-github": "workspace:^" + "@backstage/plugin-search-backend": "workspace:^" + "@backstage/plugin-search-backend-module-catalog": "workspace:^" + "@backstage/plugin-search-backend-module-explore": "workspace:^" + "@backstage/plugin-search-backend-module-techdocs": "workspace:^" + "@backstage/plugin-search-backend-node": "workspace:^" + "@backstage/plugin-signals-backend": "workspace:^" + "@backstage/plugin-techdocs-backend": "workspace:^" + languageName: unknown + linkType: soft + "execa@npm:8.0.1": version: 8.0.1 resolution: "execa@npm:8.0.1" From b192752d17f7e9a2a32b0207533f039bece606a6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 6 May 2024 15:17:14 +0200 Subject: [PATCH 249/567] changesets: added changeset for backend-next readme updates Signed-off-by: Patrik Oldsberg --- .changeset/cold-cougars-float.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/cold-cougars-float.md diff --git a/.changeset/cold-cougars-float.md b/.changeset/cold-cougars-float.md new file mode 100644 index 0000000000..1ddc78a34b --- /dev/null +++ b/.changeset/cold-cougars-float.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend-module-unprocessed': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/plugin-search-backend-module-catalog': patch +--- + +Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. From 42e1628aaf82f7a162aa802d399ca186ff7b452e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 13:29:24 +0000 Subject: [PATCH 250/567] chore(deps): update dependency @types/nodemailer to v6.4.15 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f90b972ae7..d8b86054e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16311,11 +16311,11 @@ __metadata: linkType: hard "@types/nodemailer@npm:^6.4.14": - version: 6.4.14 - resolution: "@types/nodemailer@npm:6.4.14" + version: 6.4.15 + resolution: "@types/nodemailer@npm:6.4.15" dependencies: "@types/node": "*" - checksum: 5f61f01dd736b17f431d1e8b320322f86460604b45df947fc4bc8999d7c7719405e349f7abba86e4fb100a464a30b52615d00dac03d9cb37562ff04487ebd310 + checksum: f6f9a2f8a669703ecc3ca6359c12345b16f6b2e5691b93c406b9af7de639c02092ec00133526e6fecd8c60d884890a7cd0f967d8e64bedab46d5c3d8be0882d7 languageName: node linkType: hard From 92f925dc3c3d5add5623ea0ab9357fda64a907b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20W=C3=BCrbach?= Date: Mon, 6 May 2024 15:30:11 +0200 Subject: [PATCH 251/567] chore: update humanitec plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Johannes Würbach --- microsite/data/plugins/humanitec.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microsite/data/plugins/humanitec.yaml b/microsite/data/plugins/humanitec.yaml index 7265b9d917..a94ca431e6 100644 --- a/microsite/data/plugins/humanitec.yaml +++ b/microsite/data/plugins/humanitec.yaml @@ -1,12 +1,12 @@ --- title: Humanitec Platform Orchestrator -author: Frontside -authorUrl: 'https://frontside.com' +author: Humanitec +authorUrl: 'https://humanitec.com' category: Deployment # A single category e.g. CI, Machine Learning, Services, Monitoring description: | Show workloads, environments and resources deployed by Humanitec Platform Orchestrator. Plugin includes an Entity ComponentCard, Backend API route and scaffolder actions. -documentation: https://github.com/thefrontside/playhouse/tree/main/plugins/humanitec +documentation: https://github.com/humanitec/humanitec-backstage-plugins iconUrl: /img/humanitec-logo.png -npmPackageName: '@frontside/backstage-plugin-humanitec' +npmPackageName: '@humanitec/backstage-plugin' addedDate: '2022-06-22' From 08b4c068a06979f01b3942bd241d11f8da3d597e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 13:30:16 +0000 Subject: [PATCH 252/567] chore(deps): update dependency @types/passport-google-oauth20 to v2.0.16 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f90b972ae7..771bd0915d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16378,13 +16378,13 @@ __metadata: linkType: hard "@types/passport-google-oauth20@npm:^2.0.3": - version: 2.0.14 - resolution: "@types/passport-google-oauth20@npm:2.0.14" + version: 2.0.16 + resolution: "@types/passport-google-oauth20@npm:2.0.16" dependencies: "@types/express": "*" "@types/passport": "*" "@types/passport-oauth2": "*" - checksum: 1f013dec6e6b168e7971ae1f7815b5eb015830d2da5bdb0d2fc5e3dbdf9ef7e39e2d1c62495a50d763abda9b59f7c1e35f83945ddfc309ba74cb46695da4a792 + checksum: 721163b179efd43dba861d8ce36687d58278d3aa30207f5b2e7a05f41814ea0b89edae062d1c2bfa11a4d28e4259cca423c6e53cc30e52402e213b90f5caf705 languageName: node linkType: hard From 7ed2f0d500d95ac46065b4b42d255c9aba0e68fd Mon Sep 17 00:00:00 2001 From: Ken Liang Date: Wed, 1 May 2024 14:51:27 -0400 Subject: [PATCH 253/567] Add additional settings Add squash options and merge method to scaffolder Signed-off-by: Ken Liang Signed-off-by: Zhi Liang --- .../src/actions/gitlab.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 22f875ca8d..8649c75323 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -56,6 +56,8 @@ export function createPublishGitlabAction(options: { auto_devops_enabled?: boolean; ci_config_path?: string; description?: string; + merge_method?: 'merge' | 'rebase_merge' | 'ff'; + squash_option?: 'default_off' | 'default_on' | 'never' | 'always'; topics?: string[]; visibility?: 'private' | 'internal' | 'public'; }; @@ -169,6 +171,16 @@ export function createPublishGitlabAction(options: { description: 'Short project description', type: 'string', }, + merge_method: { + title: 'Merge Method to use', + description: 'Merge Methods (merge, rebase_merge, ff)', + type: 'string', + }, + squash_option: { + title: 'Squash option', + description: 'Set squash option for the project (never, always, default_on, default_off', + type: 'string', + }, topics: { title: 'Topic labels', description: 'Topic labels to apply on the repository', From f884b3d6ab47dd304166da0db0cf4c975e74136e Mon Sep 17 00:00:00 2001 From: Zhi Liang Date: Wed, 1 May 2024 15:33:54 -0400 Subject: [PATCH 254/567] add example for ff merge and squash Signed-off-by: Zhi Liang --- .../src/actions/gitlab.examples.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts index ad032e9017..534177abf6 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts @@ -87,6 +87,25 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Initializes a GitLab repository with fast forward merge and always squash settings.', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gitlab', + name: 'Publish to GitLab', + input: { + repoUrl: 'gitlab.com?repo=project_name&owner=group_name', + settings: { + merge_method: 'ff', + squash_option: 'always', + }, + }, + }, + ], + }), + }, { description: 'Initializes a GitLab repository with branch settings.', example: yaml.stringify({ From e11a28631a9f3e969270702386e3cbd2e98edb42 Mon Sep 17 00:00:00 2001 From: Zhi Liang Date: Wed, 1 May 2024 15:35:01 -0400 Subject: [PATCH 255/567] fix up enum and typos Signed-off-by: Zhi Liang --- .../scaffolder-backend-module-gitlab/src/actions/gitlab.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 8649c75323..3dd6cb324d 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -175,11 +175,13 @@ export function createPublishGitlabAction(options: { title: 'Merge Method to use', description: 'Merge Methods (merge, rebase_merge, ff)', type: 'string', + enum: ['merge', 'rebase_merge', 'ff'], }, squash_option: { title: 'Squash option', - description: 'Set squash option for the project (never, always, default_on, default_off', + description: 'Set squash option for the project (never, always, default_on, default_off)', type: 'string', + enum: ['default_off', 'default_on', 'never', 'always'], }, topics: { title: 'Topic labels', From 69c57590850f0abeb182d059a9cc8a649d3dc6d7 Mon Sep 17 00:00:00 2001 From: Zhi Liang Date: Wed, 1 May 2024 18:11:39 -0400 Subject: [PATCH 256/567] prettier Signed-off-by: Zhi Liang --- .../src/actions/gitlab.examples.ts | 3 ++- plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts index 534177abf6..9746bfabe2 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts @@ -88,7 +88,8 @@ export const examples: TemplateExample[] = [ }), }, { - description: 'Initializes a GitLab repository with fast forward merge and always squash settings.', + description: + 'Initializes a GitLab repository with fast forward merge and always squash settings.', example: yaml.stringify({ steps: [ { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 3dd6cb324d..020e8a9c18 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -179,7 +179,8 @@ export function createPublishGitlabAction(options: { }, squash_option: { title: 'Squash option', - description: 'Set squash option for the project (never, always, default_on, default_off)', + description: + 'Set squash option for the project (never, always, default_on, default_off)', type: 'string', enum: ['default_off', 'default_on', 'never', 'always'], }, From 8fa8a00916413909c12c142dd6c9290f7857a076 Mon Sep 17 00:00:00 2001 From: Zhi Liang Date: Wed, 1 May 2024 18:15:19 -0400 Subject: [PATCH 257/567] Add merge_method and squash_option for project creation Signed-off-by: Zhi Liang --- .changeset/dirty-chairs-march.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dirty-chairs-march.md diff --git a/.changeset/dirty-chairs-march.md b/.changeset/dirty-chairs-march.md new file mode 100644 index 0000000000..8b4e677b8a --- /dev/null +++ b/.changeset/dirty-chairs-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Add merge_method and squash_option for project creation From c1483e4b4fad92b9e84c8e0d56f8ba77336dd27e Mon Sep 17 00:00:00 2001 From: Zhi Liang Date: Wed, 1 May 2024 19:05:46 -0400 Subject: [PATCH 258/567] add merge_method and squash_options Signed-off-by: Zhi Liang --- .../api-report.md | 308 ++++++++---------- 1 file changed, 141 insertions(+), 167 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 4fb965eb13..f054136f94 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts + import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; @@ -11,188 +12,160 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public export const createGitlabGroupEnsureExistsAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - path: string[]; - repoUrl: string; - token?: string | undefined; - }, - { - groupId?: number | undefined; - } ->; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< { +path: string[]; +repoUrl: string; +token?: string | undefined; +}, { +groupId?: number | undefined; +}>; // @public export const createGitlabIssueAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - title: string; - repoUrl: string; - projectId: number; - token?: string | undefined; - assignees?: number[] | undefined; - confidential?: boolean | undefined; - description?: string | undefined; - createdAt?: string | undefined; - dueDate?: string | undefined; - discussionToResolve?: string | undefined; - epicId?: number | undefined; - labels?: string | undefined; - issueType?: IssueType | undefined; - mergeRequestToResolveDiscussionsOf?: number | undefined; - milestoneId?: number | undefined; - weight?: number | undefined; - }, - { - issueUrl: string; - issueId: number; - issueIid: number; - } ->; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< { +title: string; +repoUrl: string; +projectId: number; +token?: string | undefined; +assignees?: number[] | undefined; +confidential?: boolean | undefined; +description?: string | undefined; +createdAt?: string | undefined; +dueDate?: string | undefined; +discussionToResolve?: string | undefined; +epicId?: number | undefined; +labels?: string | undefined; +issueType?: IssueType | undefined; +mergeRequestToResolveDiscussionsOf?: number | undefined; +milestoneId?: number | undefined; +weight?: number | undefined; +}, { +issueUrl: string; +issueId: number; +issueIid: number; +}>; // @public export const createGitlabProjectAccessTokenAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - repoUrl: string; - projectId: string | number; - token?: string | undefined; - name?: string | undefined; - accessLevel?: number | undefined; - scopes?: string[] | undefined; - expiresAt?: string | undefined; - }, - { - access_token: string; - } ->; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< { +repoUrl: string; +projectId: string | number; +token?: string | undefined; +name?: string | undefined; +accessLevel?: number | undefined; +scopes?: string[] | undefined; +expiresAt?: string | undefined; +}, { +access_token: string; +}>; // @public export const createGitlabProjectDeployTokenAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - name: string; - repoUrl: string; - projectId: string | number; - token?: string | undefined; - username?: string | undefined; - scopes?: string[] | undefined; - }, - { - user: string; - deploy_token: string; - } ->; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< { +name: string; +repoUrl: string; +projectId: string | number; +token?: string | undefined; +username?: string | undefined; +scopes?: string[] | undefined; +}, { +user: string; +deploy_token: string; +}>; // @public export const createGitlabProjectVariableAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - key: string; - value: string; - repoUrl: string; - projectId: string | number; - variableType: string; - token?: string | undefined; - variableProtected?: boolean | undefined; - masked?: boolean | undefined; - raw?: boolean | undefined; - environmentScope?: string | undefined; - }, - JsonObject ->; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< { +key: string; +value: string; +repoUrl: string; +projectId: string | number; +variableType: string; +token?: string | undefined; +variableProtected?: boolean | undefined; +masked?: boolean | undefined; +raw?: boolean | undefined; +environmentScope?: string | undefined; +}, JsonObject>; // @public export const createGitlabRepoPushAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - repoUrl: string; - branchName: string; - commitMessage: string; - sourcePath?: string | undefined; - targetPath?: string | undefined; - token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; - }, - JsonObject ->; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< { +repoUrl: string; +branchName: string; +commitMessage: string; +sourcePath?: string | undefined; +targetPath?: string | undefined; +token?: string | undefined; +commitAction?: "update" | "create" | "delete" | undefined; +}, JsonObject>; // @public export function createPublishGitlabAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction< - { - repoUrl: string; - defaultBranch?: string | undefined; - repoVisibility?: 'internal' | 'private' | 'public' | undefined; - sourcePath?: string | undefined; - token?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - setUserAsOwner?: boolean | undefined; - topics?: string[] | undefined; - settings?: - | { - path?: string | undefined; - auto_devops_enabled?: boolean | undefined; - ci_config_path?: string | undefined; - description?: string | undefined; - topics?: string[] | undefined; - visibility?: 'internal' | 'private' | 'public' | undefined; - } - | undefined; - branches?: - | { - name: string; - protect?: boolean | undefined; - create?: boolean | undefined; - ref?: string | undefined; - }[] - | undefined; - projectVariables?: - | { - key: string; - value: string; - description?: string | undefined; - variable_type?: string | undefined; - protected?: boolean | undefined; - masked?: boolean | undefined; - raw?: boolean | undefined; - environment_scope?: string | undefined; - }[] - | undefined; - }, - JsonObject ->; + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< { +repoUrl: string; +defaultBranch?: string | undefined; +repoVisibility?: "internal" | "private" | "public" | undefined; +sourcePath?: string | undefined; +token?: string | undefined; +gitCommitMessage?: string | undefined; +gitAuthorName?: string | undefined; +gitAuthorEmail?: string | undefined; +setUserAsOwner?: boolean | undefined; +topics?: string[] | undefined; +settings?: { +path?: string | undefined; +auto_devops_enabled?: boolean | undefined; +ci_config_path?: string | undefined; +description?: string | undefined; +merge_method?: "merge" | "ff" | "rebase_merge" | undefined; +squash_option?: "always" | "never" | "default_on" | "default_off" | undefined; +topics?: string[] | undefined; +visibility?: "internal" | "private" | "public" | undefined; +} | undefined; +branches?: { +name: string; +protect?: boolean | undefined; +create?: boolean | undefined; +ref?: string | undefined; +}[] | undefined; +projectVariables?: { +key: string; +value: string; +description?: string | undefined; +variable_type?: string | undefined; +protected?: boolean | undefined; +masked?: boolean | undefined; +raw?: boolean | undefined; +environment_scope?: string | undefined; +}[] | undefined; +}, JsonObject>; // @public export const createPublishGitlabMergeRequestAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< - { - repoUrl: string; - title: string; - description: string; - branchName: string; - targetBranchName?: string | undefined; - sourcePath?: string | undefined; - targetPath?: string | undefined; - token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; - projectid?: string | undefined; - removeSourceBranch?: boolean | undefined; - assignee?: string | undefined; - }, - JsonObject ->; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< { +repoUrl: string; +title: string; +description: string; +branchName: string; +targetBranchName?: string | undefined; +sourcePath?: string | undefined; +targetPath?: string | undefined; +token?: string | undefined; +commitAction?: "update" | "create" | "delete" | undefined; +projectid?: string | undefined; +removeSourceBranch?: boolean | undefined; +assignee?: string | undefined; +}, JsonObject>; // @public const gitlabModule: () => BackendFeature; @@ -200,11 +173,12 @@ export default gitlabModule; // @public export enum IssueType { - // (undocumented) - INCIDENT = 'incident', - // (undocumented) - ISSUE = 'issue', - // (undocumented) - TEST = 'test_case', + // (undocumented) + INCIDENT = "incident", + // (undocumented) + ISSUE = "issue", + // (undocumented) + TEST = "test_case" } + ``` From f5f7edafd613e28efbd1eb0f685c5f76238d6412 Mon Sep 17 00:00:00 2001 From: Zhi Liang Date: Wed, 1 May 2024 19:22:17 -0400 Subject: [PATCH 259/567] fix changelog method Signed-off-by: Zhi Liang --- .changeset/dirty-chairs-march.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/dirty-chairs-march.md b/.changeset/dirty-chairs-march.md index 8b4e677b8a..c4e22657a6 100644 --- a/.changeset/dirty-chairs-march.md +++ b/.changeset/dirty-chairs-march.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-gitlab': patch --- -Add merge_method and squash_option for project creation +Add merge method and squash option for project creation From 31695c4a13b6a0fe78a048a8b82c7e94aa8a86ce Mon Sep 17 00:00:00 2001 From: Zhi Liang Date: Thu, 2 May 2024 10:09:48 -0400 Subject: [PATCH 260/567] fix prettier formatting Signed-off-by: Zhi Liang --- .../api-report.md | 315 ++++++++++-------- 1 file changed, 174 insertions(+), 141 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index f054136f94..1f43509047 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts - import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; @@ -12,160 +11,195 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public export const createGitlabGroupEnsureExistsAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< { -path: string[]; -repoUrl: string; -token?: string | undefined; -}, { -groupId?: number | undefined; -}>; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + path: string[]; + repoUrl: string; + token?: string | undefined; + }, + { + groupId?: number | undefined; + } +>; // @public export const createGitlabIssueAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< { -title: string; -repoUrl: string; -projectId: number; -token?: string | undefined; -assignees?: number[] | undefined; -confidential?: boolean | undefined; -description?: string | undefined; -createdAt?: string | undefined; -dueDate?: string | undefined; -discussionToResolve?: string | undefined; -epicId?: number | undefined; -labels?: string | undefined; -issueType?: IssueType | undefined; -mergeRequestToResolveDiscussionsOf?: number | undefined; -milestoneId?: number | undefined; -weight?: number | undefined; -}, { -issueUrl: string; -issueId: number; -issueIid: number; -}>; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + title: string; + repoUrl: string; + projectId: number; + token?: string | undefined; + assignees?: number[] | undefined; + confidential?: boolean | undefined; + description?: string | undefined; + createdAt?: string | undefined; + dueDate?: string | undefined; + discussionToResolve?: string | undefined; + epicId?: number | undefined; + labels?: string | undefined; + issueType?: IssueType | undefined; + mergeRequestToResolveDiscussionsOf?: number | undefined; + milestoneId?: number | undefined; + weight?: number | undefined; + }, + { + issueUrl: string; + issueId: number; + issueIid: number; + } +>; // @public export const createGitlabProjectAccessTokenAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< { -repoUrl: string; -projectId: string | number; -token?: string | undefined; -name?: string | undefined; -accessLevel?: number | undefined; -scopes?: string[] | undefined; -expiresAt?: string | undefined; -}, { -access_token: string; -}>; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + projectId: string | number; + token?: string | undefined; + name?: string | undefined; + accessLevel?: number | undefined; + scopes?: string[] | undefined; + expiresAt?: string | undefined; + }, + { + access_token: string; + } +>; // @public export const createGitlabProjectDeployTokenAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< { -name: string; -repoUrl: string; -projectId: string | number; -token?: string | undefined; -username?: string | undefined; -scopes?: string[] | undefined; -}, { -user: string; -deploy_token: string; -}>; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + name: string; + repoUrl: string; + projectId: string | number; + token?: string | undefined; + username?: string | undefined; + scopes?: string[] | undefined; + }, + { + user: string; + deploy_token: string; + } +>; // @public export const createGitlabProjectVariableAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< { -key: string; -value: string; -repoUrl: string; -projectId: string | number; -variableType: string; -token?: string | undefined; -variableProtected?: boolean | undefined; -masked?: boolean | undefined; -raw?: boolean | undefined; -environmentScope?: string | undefined; -}, JsonObject>; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + key: string; + value: string; + repoUrl: string; + projectId: string | number; + variableType: string; + token?: string | undefined; + variableProtected?: boolean | undefined; + masked?: boolean | undefined; + raw?: boolean | undefined; + environmentScope?: string | undefined; + }, + JsonObject +>; // @public export const createGitlabRepoPushAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< { -repoUrl: string; -branchName: string; -commitMessage: string; -sourcePath?: string | undefined; -targetPath?: string | undefined; -token?: string | undefined; -commitAction?: "update" | "create" | "delete" | undefined; -}, JsonObject>; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + branchName: string; + commitMessage: string; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'update' | 'create' | 'delete' | undefined; + }, + JsonObject +>; // @public export function createPublishGitlabAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction< { -repoUrl: string; -defaultBranch?: string | undefined; -repoVisibility?: "internal" | "private" | "public" | undefined; -sourcePath?: string | undefined; -token?: string | undefined; -gitCommitMessage?: string | undefined; -gitAuthorName?: string | undefined; -gitAuthorEmail?: string | undefined; -setUserAsOwner?: boolean | undefined; -topics?: string[] | undefined; -settings?: { -path?: string | undefined; -auto_devops_enabled?: boolean | undefined; -ci_config_path?: string | undefined; -description?: string | undefined; -merge_method?: "merge" | "ff" | "rebase_merge" | undefined; -squash_option?: "always" | "never" | "default_on" | "default_off" | undefined; -topics?: string[] | undefined; -visibility?: "internal" | "private" | "public" | undefined; -} | undefined; -branches?: { -name: string; -protect?: boolean | undefined; -create?: boolean | undefined; -ref?: string | undefined; -}[] | undefined; -projectVariables?: { -key: string; -value: string; -description?: string | undefined; -variable_type?: string | undefined; -protected?: boolean | undefined; -masked?: boolean | undefined; -raw?: boolean | undefined; -environment_scope?: string | undefined; -}[] | undefined; -}, JsonObject>; + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + defaultBranch?: string | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + setUserAsOwner?: boolean | undefined; + topics?: string[] | undefined; + settings?: + | { + path?: string | undefined; + auto_devops_enabled?: boolean | undefined; + ci_config_path?: string | undefined; + description?: string | undefined; + merge_method?: 'merge' | 'ff' | 'rebase_merge' | undefined; + squash_option?: + | 'always' + | 'never' + | 'default_on' + | 'default_off' + | undefined; + topics?: string[] | undefined; + visibility?: 'internal' | 'private' | 'public' | undefined; + } + | undefined; + branches?: + | { + name: string; + protect?: boolean | undefined; + create?: boolean | undefined; + ref?: string | undefined; + }[] + | undefined; + projectVariables?: + | { + key: string; + value: string; + description?: string | undefined; + variable_type?: string | undefined; + protected?: boolean | undefined; + masked?: boolean | undefined; + raw?: boolean | undefined; + environment_scope?: string | undefined; + }[] + | undefined; + }, + JsonObject +>; // @public export const createPublishGitlabMergeRequestAction: (options: { - integrations: ScmIntegrationRegistry; -}) => TemplateAction< { -repoUrl: string; -title: string; -description: string; -branchName: string; -targetBranchName?: string | undefined; -sourcePath?: string | undefined; -targetPath?: string | undefined; -token?: string | undefined; -commitAction?: "update" | "create" | "delete" | undefined; -projectid?: string | undefined; -removeSourceBranch?: boolean | undefined; -assignee?: string | undefined; -}, JsonObject>; + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + title: string; + description: string; + branchName: string; + targetBranchName?: string | undefined; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'update' | 'create' | 'delete' | undefined; + projectid?: string | undefined; + removeSourceBranch?: boolean | undefined; + assignee?: string | undefined; + }, + JsonObject +>; // @public const gitlabModule: () => BackendFeature; @@ -173,12 +207,11 @@ export default gitlabModule; // @public export enum IssueType { - // (undocumented) - INCIDENT = "incident", - // (undocumented) - ISSUE = "issue", - // (undocumented) - TEST = "test_case" + // (undocumented) + INCIDENT = 'incident', + // (undocumented) + ISSUE = 'issue', + // (undocumented) + TEST = 'test_case', } - ``` From f73bd268715c913f68375517e230108e2ba997df Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 6 May 2024 15:40:07 +0200 Subject: [PATCH 261/567] chore: update api-reports Signed-off-by: blam --- plugins/scaffolder-backend-module-gitlab/api-report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 1f43509047..ca98aa558b 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -118,7 +118,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'create' | 'delete' | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; }, JsonObject >; @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'create' | 'delete' | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; From e0a1653eb49e9c0ac05c91ba5425ef451b305c13 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 6 May 2024 15:47:01 +0200 Subject: [PATCH 262/567] chore: api-report updates Signed-off-by: blam --- .../api-report.md | 24 +++++------ plugins/scaffolder-backend/api-report.md | 6 +-- plugins/scaffolder/api-report.md | 42 +++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 4fb965eb13..ac4c45f07d 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -31,19 +31,19 @@ export const createGitlabIssueAction: (options: { title: string; repoUrl: string; projectId: number; + labels?: string | undefined; + description?: string | undefined; + weight?: number | undefined; token?: string | undefined; assignees?: number[] | undefined; - confidential?: boolean | undefined; - description?: string | undefined; createdAt?: string | undefined; + confidential?: boolean | undefined; + milestoneId?: number | undefined; + epicId?: number | undefined; dueDate?: string | undefined; discussionToResolve?: string | undefined; - epicId?: number | undefined; - labels?: string | undefined; issueType?: IssueType | undefined; mergeRequestToResolveDiscussionsOf?: number | undefined; - milestoneId?: number | undefined; - weight?: number | undefined; }, { issueUrl: string; @@ -59,11 +59,11 @@ export const createGitlabProjectAccessTokenAction: (options: { { repoUrl: string; projectId: string | number; - token?: string | undefined; name?: string | undefined; - accessLevel?: number | undefined; + token?: string | undefined; scopes?: string[] | undefined; expiresAt?: string | undefined; + accessLevel?: number | undefined; }, { access_token: string; @@ -78,8 +78,8 @@ export const createGitlabProjectDeployTokenAction: (options: { name: string; repoUrl: string; projectId: string | number; - token?: string | undefined; username?: string | undefined; + token?: string | undefined; scopes?: string[] | undefined; }, { @@ -98,11 +98,11 @@ export const createGitlabProjectVariableAction: (options: { repoUrl: string; projectId: string | number; variableType: string; - token?: string | undefined; - variableProtected?: boolean | undefined; - masked?: boolean | undefined; raw?: boolean | undefined; + token?: string | undefined; + masked?: boolean | undefined; environmentScope?: string | undefined; + variableProtected?: boolean | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 6af1ac04a6..9de99c7a5b 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -136,15 +136,15 @@ export function createFetchCatalogEntityAction(options: { auth?: AuthService; }): TemplateAction_2< { - entityRef?: string | undefined; - entityRefs?: string[] | undefined; optional?: boolean | undefined; defaultKind?: string | undefined; defaultNamespace?: string | undefined; + entityRef?: string | undefined; + entityRefs?: string[] | undefined; }, { - entity?: any; entities?: any[] | undefined; + entity?: any; } >; diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index ccea88e27c..5c766af890 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -79,10 +79,10 @@ export const EntityNamePickerFieldExtension: FieldExtensionComponent_2< export const EntityPickerFieldExtension: FieldExtensionComponent_2< string, { - allowedKinds?: string[] | undefined; defaultKind?: string | undefined; - allowArbitraryValues?: boolean | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; catalogFilter?: | Record< string, @@ -108,10 +108,10 @@ export const EntityPickerFieldExtension: FieldExtensionComponent_2< export const EntityPickerFieldSchema: FieldSchema< string, { - allowedKinds?: string[] | undefined; defaultKind?: string | undefined; - allowArbitraryValues?: boolean | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; catalogFilter?: | Record< string, @@ -141,9 +141,9 @@ export type EntityPickerUiOptions = export const EntityTagsPickerFieldExtension: FieldExtensionComponent_2< string[], { - kinds?: string[] | undefined; - showCounts?: boolean | undefined; helperText?: string | undefined; + showCounts?: boolean | undefined; + kinds?: string[] | undefined; } >; @@ -151,9 +151,9 @@ export const EntityTagsPickerFieldExtension: FieldExtensionComponent_2< export const EntityTagsPickerFieldSchema: FieldSchema< string[], { - kinds?: string[] | undefined; - showCounts?: boolean | undefined; helperText?: string | undefined; + showCounts?: boolean | undefined; + kinds?: string[] | undefined; } >; @@ -215,8 +215,8 @@ export const MultiEntityPickerFieldExtension: FieldExtensionComponent_2< string[], { defaultKind?: string | undefined; - allowArbitraryValues?: boolean | undefined; defaultNamespace?: string | false | undefined; + allowArbitraryValues?: boolean | undefined; catalogFilter?: | Record< string, @@ -267,10 +267,10 @@ export type MyGroupsPickerUiOptions = export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2< string, { - allowedKinds?: string[] | undefined; defaultKind?: string | undefined; - allowArbitraryValues?: boolean | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; catalogFilter?: | Record< string, @@ -296,10 +296,10 @@ export const OwnedEntityPickerFieldExtension: FieldExtensionComponent_2< export const OwnedEntityPickerFieldSchema: FieldSchema< string, { - allowedKinds?: string[] | undefined; defaultKind?: string | undefined; - allowArbitraryValues?: boolean | undefined; defaultNamespace?: string | false | undefined; + allowedKinds?: string[] | undefined; + allowArbitraryValues?: boolean | undefined; catalogFilter?: | Record< string, @@ -329,9 +329,9 @@ export type OwnedEntityPickerUiOptions = export const OwnerPickerFieldExtension: FieldExtensionComponent_2< string, { + defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - defaultNamespace?: string | false | undefined; catalogFilter?: | Record< string, @@ -357,9 +357,9 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent_2< export const OwnerPickerFieldSchema: FieldSchema< string, { + defaultNamespace?: string | false | undefined; allowedKinds?: string[] | undefined; allowArbitraryValues?: boolean | undefined; - defaultNamespace?: string | false | undefined; catalogFilter?: | Record< string, @@ -407,12 +407,12 @@ export const RepoUrlPickerFieldExtension: FieldExtensionComponent_2< secretsKey: string; additionalScopes?: | { - gitea?: string[] | undefined; - gerrit?: string[] | undefined; + azure?: string[] | undefined; github?: string[] | undefined; gitlab?: string[] | undefined; bitbucket?: string[] | undefined; - azure?: string[] | undefined; + gerrit?: string[] | undefined; + gitea?: string[] | undefined; } | undefined; } @@ -434,12 +434,12 @@ export const RepoUrlPickerFieldSchema: FieldSchema< secretsKey: string; additionalScopes?: | { - gitea?: string[] | undefined; - gerrit?: string[] | undefined; + azure?: string[] | undefined; github?: string[] | undefined; gitlab?: string[] | undefined; bitbucket?: string[] | undefined; - azure?: string[] | undefined; + gerrit?: string[] | undefined; + gitea?: string[] | undefined; } | undefined; } From 1cda362e8497a7b5c7dd1889eddc3b8ef974f922 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 14:15:52 +0000 Subject: [PATCH 263/567] chore(deps): update dependency @types/pg to v8.11.6 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3e3d6127f0..20f60491e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16438,13 +16438,13 @@ __metadata: linkType: hard "@types/pg@npm:^8.6.6": - version: 8.11.5 - resolution: "@types/pg@npm:8.11.5" + version: 8.11.6 + resolution: "@types/pg@npm:8.11.6" dependencies: "@types/node": "*" pg-protocol: "*" pg-types: ^4.0.1 - checksum: 7346d3df959a8d279cba581c8ee93ed7e331e516c5d8c6866029b9f70eadecb8400818bdc9994d69dd75ccab5bdb7e5a1fc16897efd2e3033fa1ccecd2d2d31a + checksum: 231f7e5bfe8b4d14cca398d24cd55f4f14f582f815b62059e6f3ee74108cf92089fbd946568ebc35fa402f238ed9c8a8c1e10e7084e83e4ca3aff75957243014 languageName: node linkType: hard From 3112aecb0d7a7d9921eee946ea4e6d6b11bcaddf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 14:32:27 +0000 Subject: [PATCH 264/567] chore(deps): update dependency @types/react-syntax-highlighter to v15.5.13 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3f4442d1e7..b9ded5141c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16564,11 +16564,11 @@ __metadata: linkType: hard "@types/react-syntax-highlighter@npm:^15.0.0": - version: 15.5.11 - resolution: "@types/react-syntax-highlighter@npm:15.5.11" + version: 15.5.13 + resolution: "@types/react-syntax-highlighter@npm:15.5.13" dependencies: "@types/react": "*" - checksum: 8363ded0138963407c909f198ddcac58d9c937b118f16a46fb3e97078dd0c6234746f9efa85f6aa660efebe357bab11047c95b57bd9508dd4b09619b1a237087 + checksum: 55f751c140eb6641b16a5644af3b6fc25223957141085758ae6898948e70eaca33d8276e86e75d5d60939aff63af1d20278aba0d3a25483266f9deee1eb468e3 languageName: node linkType: hard From 83e08347457854d58f4d253abdab6d17784c292c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 15:17:01 +0000 Subject: [PATCH 265/567] chore(deps): update step-security/harden-runner action to v2.7.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes-comment.yml | 2 +- .github/workflows/api-breaking-changes.yml | 2 +- .github/workflows/automate_area-labels.yml | 2 +- .github/workflows/automate_changeset_feedback.yml | 2 +- .github/workflows/automate_merge_message.yml | 2 +- .github/workflows/automate_stale.yml | 2 +- .github/workflows/ci-noop.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/cron.yml | 2 +- .github/workflows/deploy_docker-image.yml | 2 +- .github/workflows/deploy_microsite.yml | 2 +- .github/workflows/deploy_nightly.yml | 2 +- .github/workflows/deploy_packages.yml | 2 +- .github/workflows/issue.yaml | 2 +- .github/workflows/pr-review-comment-trigger.yaml | 2 +- .github/workflows/pr-review-comment.yaml | 2 +- .github/workflows/pr.yaml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_code-formatting.yml | 2 +- .github/workflows/sync_dependabot-changesets.yml | 2 +- .github/workflows/sync_release-manifest.yml | 2 +- .github/workflows/sync_renovate-changesets.yml | 2 +- .github/workflows/sync_snyk-github-issues.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/sync_version-packages.yml | 2 +- .github/workflows/uffizzi-build.yml | 6 +++--- .github/workflows/uffizzi-preview.yaml | 2 +- .github/workflows/verify_accessibility-noop.yml | 2 +- .github/workflows/verify_accessibility.yml | 2 +- .github/workflows/verify_codeql.yml | 2 +- .github/workflows/verify_docs-quality.yml | 2 +- .github/workflows/verify_e2e-kubernetes-noop.yml | 2 +- .github/workflows/verify_e2e-kubernetes.yml | 2 +- .github/workflows/verify_e2e-linux-noop.yml | 2 +- .github/workflows/verify_e2e-linux.yml | 2 +- .github/workflows/verify_e2e-techdocs.yml | 2 +- .github/workflows/verify_e2e-windows-noop.yml | 2 +- .github/workflows/verify_e2e-windows.yml | 2 +- .github/workflows/verify_fossa.yml | 2 +- .github/workflows/verify_microsite-noop.yml | 2 +- .github/workflows/verify_microsite.yml | 2 +- .github/workflows/verify_microsite_accessibility-noop.yml | 2 +- .github/workflows/verify_microsite_accessibility.yml | 2 +- .github/workflows/verify_storybook-noop.yml | 2 +- .github/workflows/verify_storybook.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- 46 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index bc90a276f0..9ae32e1d4a 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -22,7 +22,7 @@ jobs: action: ${{ steps.event.outputs.ACTION }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: disable-sudo: true egress-policy: block diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index ef09fa10ae..84161e3010 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -14,7 +14,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/automate_area-labels.yml b/.github/workflows/automate_area-labels.yml index 632624bd50..74f577f360 100644 --- a/.github/workflows/automate_area-labels.yml +++ b/.github/workflows/automate_area-labels.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index fefc73bd9f..ef1a258239 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index d6520d16a6..dd8895a752 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index c7674e853d..afb8f3b838 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/ci-noop.yml b/.github/workflows/ci-noop.yml index 2c66da768f..4dc8594bc9 100644 --- a/.github/workflows/ci-noop.yml +++ b/.github/workflows/ci-noop.yml @@ -40,7 +40,7 @@ jobs: name: Test ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c819ae8130..c538ec8957 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: name: Install ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit @@ -64,7 +64,7 @@ jobs: name: Verify ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index c32d50ced5..7dcb4a9c0f 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 8c263fdd4f..d5f5eee35e 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index b955b39b59..037fb27e8d 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index a3a1b5e283..ab8ea3c773 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 0c44d32384..44222275ad 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -144,7 +144,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml index f8f5efacc4..7afc0a81bb 100644 --- a/.github/workflows/issue.yaml +++ b/.github/workflows/issue.yaml @@ -10,7 +10,7 @@ jobs: if: github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/pr-review-comment-trigger.yaml b/.github/workflows/pr-review-comment-trigger.yaml index 5e7d71cbf4..bea1618608 100644 --- a/.github/workflows/pr-review-comment-trigger.yaml +++ b/.github/workflows/pr-review-comment-trigger.yaml @@ -20,7 +20,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml index 72256a8c6e..232089e2f3 100644 --- a/.github/workflows/pr-review-comment.yaml +++ b/.github/workflows/pr-review-comment.yaml @@ -17,7 +17,7 @@ jobs: steps: # Inspired by https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#using-data-from-the-triggering-workflow - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 3aa20b1c96..779137b52a 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -18,7 +18,7 @@ jobs: if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request ) steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3c36cae863..8a069d476d 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index a59eea9c9b..7f10ae60df 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 88997ea5dd..7c8e634fb8 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -11,7 +11,7 @@ jobs: if: github.actor == 'dependabot[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 1549ec0e90..7fe8817731 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index e95a68ac2f..e1c5656d08 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -11,7 +11,7 @@ jobs: if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index bebbbc4fbe..1948807215 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index cb3efcfac3..43c613b641 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 82a0eae7be..ed69fd3c24 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 1a742bdf24..eab0a61e46 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -26,7 +26,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit @@ -84,7 +84,7 @@ jobs: - build-backstage steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit @@ -118,7 +118,7 @@ jobs: if: ${{ github.event.action == 'closed' }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/uffizzi-preview.yaml b/.github/workflows/uffizzi-preview.yaml index b4c5aa3caa..8d0f558b1f 100644 --- a/.github/workflows/uffizzi-preview.yaml +++ b/.github/workflows/uffizzi-preview.yaml @@ -23,7 +23,7 @@ jobs: action: ${{ steps.event.outputs.ACTION }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: disable-sudo: true egress-policy: block diff --git a/.github/workflows/verify_accessibility-noop.yml b/.github/workflows/verify_accessibility-noop.yml index 07c92b5b5d..7ec67cbf0f 100644 --- a/.github/workflows/verify_accessibility-noop.yml +++ b/.github/workflows/verify_accessibility-noop.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index b4615fee0a..2c0e10c118 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 60cb89a045..f028147b85 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -42,7 +42,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 2eb7608ccd..21d3563b57 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-kubernetes-noop.yml b/.github/workflows/verify_e2e-kubernetes-noop.yml index 8d8b14d5ba..6352abce70 100644 --- a/.github/workflows/verify_e2e-kubernetes-noop.yml +++ b/.github/workflows/verify_e2e-kubernetes-noop.yml @@ -23,7 +23,7 @@ jobs: name: Kubernetes ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index 3c425c25e9..ffaa7971e6 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -22,7 +22,7 @@ jobs: name: Kubernetes ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-linux-noop.yml b/.github/workflows/verify_e2e-linux-noop.yml index 93d48caf22..2b7eb1d84b 100644 --- a/.github/workflows/verify_e2e-linux-noop.yml +++ b/.github/workflows/verify_e2e-linux-noop.yml @@ -29,7 +29,7 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 75c7a800d8..6ff3b9eecf 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -41,7 +41,7 @@ jobs: name: E2E Linux ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index d5f2594285..ad0950392f 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -30,7 +30,7 @@ jobs: name: Techdocs steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-windows-noop.yml b/.github/workflows/verify_e2e-windows-noop.yml index a7ebdba4b5..fd90b86b0d 100644 --- a/.github/workflows/verify_e2e-windows-noop.yml +++ b/.github/workflows/verify_e2e-windows-noop.yml @@ -25,7 +25,7 @@ jobs: name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index a6fa469666..7a823a85e1 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -31,7 +31,7 @@ jobs: name: E2E Windows ${{ matrix.node-version }} steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index ccc9e4509a..4c9b1516a2 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite-noop.yml b/.github/workflows/verify_microsite-noop.yml index ad3e67ba70..a9a82b140b 100644 --- a/.github/workflows/verify_microsite-noop.yml +++ b/.github/workflows/verify_microsite-noop.yml @@ -21,7 +21,7 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index d5cda46253..724fac7fef 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -24,7 +24,7 @@ jobs: name: Microsite steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite_accessibility-noop.yml b/.github/workflows/verify_microsite_accessibility-noop.yml index 4b63dc41cc..8ac2672b22 100644 --- a/.github/workflows/verify_microsite_accessibility-noop.yml +++ b/.github/workflows/verify_microsite_accessibility-noop.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index 19417c5cee..af2364e743 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_storybook-noop.yml b/.github/workflows/verify_storybook-noop.yml index 74aed57f5c..e98da4ae02 100644 --- a/.github/workflows/verify_storybook-noop.yml +++ b/.github/workflows/verify_storybook-noop.yml @@ -28,7 +28,7 @@ jobs: name: Storybook steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 6a37f5abb6..3cdb0de344 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -28,7 +28,7 @@ jobs: name: Storybook steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index c7ff8f5ca9..0c54cfb287 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 with: egress-policy: audit From 2a3676b8b64a2be49aa2b8eb781d7cf5ee8a700b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 15:28:34 +0000 Subject: [PATCH 266/567] fix(deps): update dependency @keyv/redis to v2.8.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 89d4b0ee45..453c15a661 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9974,11 +9974,11 @@ __metadata: linkType: hard "@keyv/redis@npm:^2.5.3": - version: 2.8.4 - resolution: "@keyv/redis@npm:2.8.4" + version: 2.8.5 + resolution: "@keyv/redis@npm:2.8.5" dependencies: - ioredis: ^5.3.2 - checksum: 088fb439dc900d6c848c187a0a3218f8c80e3d5df0ec94995d2245b701d59c9d99d4ccc2b48b12682e20d1c0653ababc2f7a51a04737c4df539f4dac82eca87b + ioredis: ^5.4.1 + checksum: 87ffec61d31fa9de128ba3e5a7b616535ddbdaa4d92cbc9e1a9fab143adf967135e9cca16e192e8f52cc1ba00ed2a7f10eca9944d7550385530dab95333e81ef languageName: node linkType: hard @@ -27145,9 +27145,9 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.3.2": - version: 5.3.2 - resolution: "ioredis@npm:5.3.2" +"ioredis@npm:^5.4.1": + version: 5.4.1 + resolution: "ioredis@npm:5.4.1" dependencies: "@ioredis/commands": ^1.1.1 cluster-key-slot: ^1.1.0 @@ -27158,7 +27158,7 @@ __metadata: redis-errors: ^1.2.0 redis-parser: ^3.0.0 standard-as-callback: ^2.1.0 - checksum: 9a23559133e862a768778301efb68ae8c2af3c33562174b54a4c2d6574b976e85c75a4c34857991af733e35c48faf4c356e7daa8fb0a3543d85ff1768c8754bc + checksum: 92210294f75800febe7544c27b07e4892480172363b11971aa575be5b68f023bfed4bc858abc9792230c153aa80409047a358f174062c14d17536aa4499fe10b languageName: node linkType: hard From 21aa820c3c186be6e3d41b59f2e009fea9cbc4fa Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 6 May 2024 18:23:35 +0200 Subject: [PATCH 267/567] wip Signed-off-by: bnechyporenko --- plugins/scaffolder-backend/api-report.md | 11 ++++- plugins/scaffolder-backend/config.d.ts | 5 +++ .../src/scaffolder/tasks/DatabaseTaskStore.ts | 6 +++ .../tasks/NunjucksWorkflowRunner.ts | 1 + .../src/scaffolder/tasks/StorageTaskBroker.ts | 42 +++++++++++++++---- .../src/scaffolder/tasks/types.ts | 2 + plugins/scaffolder-node/api-report.md | 2 + plugins/scaffolder-node/src/tasks/types.ts | 2 + 8 files changed, 63 insertions(+), 8 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 3ef21f4c98..0c01fd6a9e 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -389,6 +389,8 @@ export class DatabaseTaskStore implements TaskStore { // (undocumented) claimTask(): Promise; // (undocumented) + cleanWorkspace({ taskId }: { taskId: string }): Promise; + // (undocumented) completeTask(options: { taskId: string; status: TaskStatus_2; @@ -540,6 +542,8 @@ export class TaskManager implements TaskContext_2 { // (undocumented) get cancelSignal(): AbortSignal; // (undocumented) + cleanWorkspace?(): Promise; + // (undocumented) complete(result: TaskCompletionState_2, metadata?: JsonObject): Promise; // (undocumented) static create( @@ -548,6 +552,7 @@ export class TaskManager implements TaskContext_2 { abortSignal: AbortSignal, logger: Logger, auth?: AuthService, + config?: Config, ): TaskManager; // (undocumented) get createdBy(): string | undefined; @@ -567,7 +572,9 @@ export class TaskManager implements TaskContext_2 { // (undocumented) getWorkspaceName(): Promise; // (undocumented) - rehydrateWorkspace(options: { + get isWorkspaceSerializationEnabled(): boolean; + // (undocumented) + rehydrateWorkspace?(options: { taskId: string; targetPath: string; }): Promise; @@ -606,6 +613,8 @@ export interface TaskStore { // (undocumented) claimTask(): Promise; // (undocumented) + cleanWorkspace?({ taskId }: { taskId: string }): Promise; + // (undocumented) completeTask(options: { taskId: string; status: TaskStatus; diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index 477defe026..754eedb55b 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -47,6 +47,11 @@ export interface Config { */ EXPERIMENTAL_recoverTasks?: boolean; + /** + * Sets the serialization of the workspace to have an ability to rerun the failed task. + */ + EXPERIMENTAL_workspaceSerialization?: boolean; + /** * Every task which is in progress state and having a last heartbeat longer than a specified timeout is going to * be attempted to recover. diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 792b74e8d2..f6f00b0441 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -522,6 +522,12 @@ export class DatabaseTaskStore implements TaskStore { await restoreWorkspace(options.targetPath, result.workspace); } + async cleanWorkspace({ taskId }: { taskId: string }): Promise { + await this.db('tasks').where({ id: taskId }).update({ + workspace: undefined, + }); + } + async serializeWorkspace(options: { path: string; taskId: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index decb05ad88..77097e97ea 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -457,6 +457,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { throw new Error(`Step ${step.name} has been cancelled.`); } + await task.cleanWorkspace?.(); await stepTrack.markSuccessful(); } catch (err) { await taskTrack.markFailed(step, err); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 51f3afc623..9f8f7df241 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -64,8 +64,16 @@ export class TaskManager implements TaskContext { abortSignal: AbortSignal, logger: Logger, auth?: AuthService, + config?: Config, ) { - const agent = new TaskManager(task, storage, abortSignal, logger, auth); + const agent = new TaskManager( + task, + storage, + abortSignal, + logger, + auth, + config, + ); agent.startTimeout(); return agent; } @@ -77,8 +85,17 @@ export class TaskManager implements TaskContext { private readonly signal: AbortSignal, private readonly logger: Logger, private readonly auth?: AuthService, + private readonly config?: Config, ) {} + get isWorkspaceSerializationEnabled(): boolean { + return ( + this.config?.getOptionalBoolean( + 'scaffolder.EXPERIMENTAL_workspaceSerialization', + ) ?? false + ); + } + get spec() { return this.task.spec; } @@ -99,11 +116,13 @@ export class TaskManager implements TaskContext { return this.task.taskId; } - async rehydrateWorkspace(options: { + async rehydrateWorkspace?(options: { taskId: string; targetPath: string; }): Promise { - return this.storage.rehydrateWorkspace?.(options); + if (this.isWorkspaceSerializationEnabled) { + this.storage.rehydrateWorkspace?.(options); + } } get done() { @@ -152,10 +171,18 @@ export class TaskManager implements TaskContext { } async serializeWorkspace?(options: { path: string }): Promise { - await this.storage.serializeWorkspace?.({ - path: options.path, - taskId: this.task.taskId, - }); + if (this.isWorkspaceSerializationEnabled) { + await this.storage.serializeWorkspace?.({ + path: options.path, + taskId: this.task.taskId, + }); + } + } + + async cleanWorkspace?(): Promise { + if (this.isWorkspaceSerializationEnabled) { + await this.storage.cleanWorkspace?.({ taskId: this.task.taskId }); + } } async complete( @@ -338,6 +365,7 @@ export class StorageTaskBroker implements TaskBroker { abortController.signal, this.logger, this.auth, + this.config, ); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 676b7e8c3a..0e0ebd706b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -217,6 +217,8 @@ export interface TaskStore { targetPath: string; }): Promise; + cleanWorkspace?({ taskId }: { taskId: string }): Promise; + serializeWorkspace?({ path, taskId, diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index a6b91bdccb..66e8ccd678 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -344,6 +344,8 @@ export interface TaskContext { // (undocumented) cancelSignal: AbortSignal; // (undocumented) + cleanWorkspace?(): Promise; + // (undocumented) complete(result: TaskCompletionState, metadata?: JsonObject): Promise; // (undocumented) createdBy?: string; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index b2da5b2f0f..a2adb97b9c 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -143,6 +143,8 @@ export interface TaskContext { serializeWorkspace?(options: { path: string }): Promise; + cleanWorkspace?(): Promise; + rehydrateWorkspace?(options: { taskId: string; targetPath: string; From a5ad56226e5044f11a68d7bdb2cd7822d41f6424 Mon Sep 17 00:00:00 2001 From: Joshua Jung Date: Mon, 6 May 2024 13:59:28 +0200 Subject: [PATCH 268/567] Update feature-flags.md Some minor updates to punctuation, code examples, and language Signed-off-by: Joshua Jung --- docs/plugins/feature-flags.md | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/plugins/feature-flags.md b/docs/plugins/feature-flags.md index 7b7a6c1435..3afb8081d5 100644 --- a/docs/plugins/feature-flags.md +++ b/docs/plugins/feature-flags.md @@ -6,39 +6,39 @@ description: Details the process of defining setting and reading a feature flag. Backstage offers the ability to define feature flags inside a plugin or during application creation. This allows you to restrict parts of your plugin to those individual users who have toggled the feature flag to on. -This page describes the process of defining setting and reading a feature flag. If you are looking for using feature flags with software templates that can be found under [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags). +This page describes the process of defining, setting and reading a feature flag. If you are looking for using feature flags specifically with software templates please see [Writing Templates](https://backstage.io/docs/features/software-templates/writing-templates#remove-sections-or-fields-based-on-feature-flags). ## Defining a Feature Flag ### In a plugin -Defining feature flag in a plugin is done by passing the name of the feature flag into the `featureFlags` array: +Defining a feature flag in a plugin is done by passing the name of the feature flag into the `featureFlags` array: -```ts -/* src/plugin.ts */ -import { createPlugin, createRouteRef } from '@backstage/core-plugin-api'; -import ExampleComponent from './components/ExampleComponent'; +```ts title="src/plugin.ts" +import { createPlugin } from '@backstage/core-plugin-api'; export const examplePlugin = createPlugin({ - id: 'example', - routes: { - root: rootRouteRef, - }, + // ... featureFlags: [{ name: 'show-example-feature' }], + // ... }); ``` ### In the application -Defining feature flag in the application is done by adding feature flags in`featureFlags` array in +Defining a feature flag in the application is done by adding feature flags in `featureFlags` array in the `createApp()` function call: -```ts +```ts title="packages/app/src/App.tsx" +import { createApp } from '@backstage/app-defaults'; + const app = createApp({ // ... featureFlags: [ { - pluginId: '', // pluginId is required for feature flags in plugins. It can be left blank for a feature flag leveraged in the application. + // pluginId is required for feature flags used in plugins. + // pluginId can be left blank for a feature flag used in the application and not in plugins. + pluginId: '', name: 'tech-radar', description: 'Enables the tech radar plugin', }, @@ -49,11 +49,9 @@ const app = createApp({ ## Enabling Feature Flags -Feature flags are defaulted to off and can be updated by individual users in the backstage interface. +Feature flags are defaulted to off and can be updated by individual users in the backstage interface. These are set by navigating to the page under `Settings` > `Feature Flags`. -These are set by navigating to the page under `Settings` > `Feature Flags`. - -The users selection is saved in the users browsers local storage. Once toggled it may be required for a user to refresh the page to see any new changes. +The user's selection is saved in the user's browser local storage. Once a feature flag is toggled it may be required for a user to refresh the page to see the change. ## FeatureFlagged Component @@ -75,7 +73,7 @@ import { FeatureFlagged } from '@backstage/core-app-api'; ## Evaluating Feature Flag State -It is also possible to test the feature flag state using the [FeatureFlags Api](https://backstage.io/docs/reference/core-plugin-api.featureflagsapi). +It is also possible to query a feature flag using the [FeatureFlags Api](https://backstage.io/docs/reference/core-plugin-api.featureflagsapi). ```ts import { useApi, featureFlagsApiRef } from '@backstage/core-plugin-api'; From ddddecb21cfe9937426d8a068674866cc6650b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 6 May 2024 17:14:30 +0200 Subject: [PATCH 269/567] frontend-app-api: always shunt mentioned extensions to the top MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/lazy-phones-worry.md | 5 ++ .../src/tree/resolveAppNodeSpecs.test.ts | 76 +++++++++++++++++-- .../src/tree/resolveAppNodeSpecs.ts | 19 ++--- .../src/wiring/createPlugin.test.ts | 2 +- 4 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 .changeset/lazy-phones-worry.md diff --git a/.changeset/lazy-phones-worry.md b/.changeset/lazy-phones-worry.md new file mode 100644 index 0000000000..2c64f81f2c --- /dev/null +++ b/.changeset/lazy-phones-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': minor +--- + +Extensions in app-config now always affect ordering. Previously, only when enabling disabled extensions did they rise to the top. diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index 82f274b960..8af2aef070 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -110,6 +110,12 @@ describe('resolveAppNodeSpecs', () => { ], }), ).toEqual([ + { + id: 'b', + extension: b, + attachTo: { id: 'derp', input: 'default' }, + disabled: false, + }, { id: 'test/a', extension: makeExt('test/a'), @@ -117,12 +123,6 @@ describe('resolveAppNodeSpecs', () => { source: pluginA, disabled: false, }, - { - id: 'b', - extension: b, - attachTo: { id: 'derp', input: 'default' }, - disabled: false, - }, ]); }); @@ -206,6 +206,70 @@ describe('resolveAppNodeSpecs', () => { ]); }); + it('should place config-mentioned instances in the order that they were listed, irrespective of if the extension was enabled or not originally', () => { + const a = makeExt('a', 'disabled'); + const b = makeExt('b', 'enabled'); + const c = makeExt('c', 'disabled'); + const d = makeExt('d', 'enabled'); + const e = makeExt('e', 'disabled'); + const f = makeExt('f', 'enabled'); + const g = makeExt('g', 'disabled'); + expect( + resolveAppNodeSpecs({ + features: [createPlugin({ id: 'empty', extensions: [] })], + builtinExtensions: [a, b, c, d, e, f, g], + parameters: [ + { id: 'e', disabled: false }, + { id: 'd', disabled: false }, + { id: 'c', disabled: false }, + ], + }), + ).toEqual([ + { + id: 'e', + extension: e, + attachTo: { id: 'root', input: 'default' }, + disabled: false, + }, + { + id: 'd', + extension: d, + attachTo: { id: 'root', input: 'default' }, + disabled: false, + }, + { + id: 'c', + extension: c, + attachTo: { id: 'root', input: 'default' }, + disabled: false, + }, + { + id: 'a', + extension: a, + attachTo: { id: 'root', input: 'default' }, + disabled: true, + }, + { + id: 'b', + extension: b, + attachTo: { id: 'root', input: 'default' }, + disabled: false, + }, + { + id: 'f', + extension: f, + attachTo: { id: 'root', input: 'default' }, + disabled: false, + }, + { + id: 'g', + extension: g, + attachTo: { id: 'root', input: 'default' }, + disabled: true, + }, + ]); + }); + it('should apply extension overrides', () => { const plugin = createPlugin({ id: 'test', diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index f056e7a49e..e31c70025d 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -184,6 +184,7 @@ export function resolveAppNodeSpecs(options: { ); } + const order = new Map(); for (const overrideParam of parameters) { const extensionId = overrideParam.id; @@ -193,11 +194,10 @@ export function resolveAppNodeSpecs(options: { ); } - const existingIndex = configuredExtensions.findIndex( + const existing = configuredExtensions.find( e => e.extension.id === extensionId, ); - if (existingIndex !== -1) { - const existing = configuredExtensions[existingIndex]; + if (existing) { if (overrideParam.attachTo) { existing.params.attachTo = overrideParam.attachTo; } @@ -209,18 +209,19 @@ export function resolveAppNodeSpecs(options: { Boolean(existing.params.disabled) !== Boolean(overrideParam.disabled) ) { existing.params.disabled = Boolean(overrideParam.disabled); - if (!existing.params.disabled) { - // bump - configuredExtensions.splice(existingIndex, 1); - configuredExtensions.push(existing); - } } + order.set(extensionId, existing); } else { throw new Error(`Extension ${extensionId} does not exist`); } } - return configuredExtensions.map(param => ({ + const orderedExtensions = [ + ...order.values(), + ...configuredExtensions.filter(e => !order.has(e.extension.id)), + ]; + + return orderedExtensions.map(param => ({ id: param.extension.id, attachTo: param.params.attachTo, extension: param.extension, diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index c180dc9719..dab8a9778a 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -187,7 +187,7 @@ describe('createPlugin', () => { await expect( screen.findByText( - 'Names: extension-1, extension-2-renamed, extension-3:child', + 'Names: extension-2-renamed, extension-1, extension-3:child', ), ).resolves.toBeInTheDocument(); }); From 5e033426086a81f9ace44442b2143000bf7d3454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 6 May 2024 22:06:47 +0200 Subject: [PATCH 270/567] added two more esm-only packages to the renovate block list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .github/renovate.json5 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1c8d611c49..5a70a25512 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -70,5 +70,13 @@ matchPackageNames: ['p-queue'], allowedVersions: '<7.0.0', }, + { + matchPackageNames: ['serialize-error'], + allowedVersions: '<9.0.0', + }, + { + matchPackageNames: ['yn'], + allowedVersions: '<5.0.0', + }, ], } From d25805fcf1ea1569d2bd5cbe0ebc42e27cb26078 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 6 May 2024 19:05:52 -0400 Subject: [PATCH 271/567] move to old/new pattern Signed-off-by: aramissennyeydd --- .../extending-the-model--old.md | 604 ++++++++++++++++++ .../software-catalog/extending-the-model.md | 29 +- 2 files changed, 612 insertions(+), 21 deletions(-) create mode 100644 docs/features/software-catalog/extending-the-model--old.md diff --git a/docs/features/software-catalog/extending-the-model--old.md b/docs/features/software-catalog/extending-the-model--old.md new file mode 100644 index 0000000000..b3d7de3a65 --- /dev/null +++ b/docs/features/software-catalog/extending-the-model--old.md @@ -0,0 +1,604 @@ +--- +id: extending-the-model +title: Extending the model +# prettier-ignore +description: Documentation on extending the catalog model +--- + +The Backstage catalog [entity data model](descriptor-format.md) is based on the +[Kubernetes objects format](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/), +and borrows a lot of its semantics as well. This page describes those semantics +at a higher level and how to extend them to fit your organization. + +Backstage comes with a number of catalog concepts out of the box: + +- There are a number of builtin versioned _kinds_, such as `Component`, `User` + etc. These encapsulate the high level concept of an entity, and define the + schema for its entity definition data. +- An entity has both a _metadata_ object and a _spec_ object at the root. +- Each kind may or may not have a _type_. For example, there are several well + known types of component, such as `service` and `website`. These clarify the + more detailed nature of the entity, and may affect what features are exposed + in the interface. +- Entities may have a number of _[annotations](well-known-annotations.md)_ on + them. These can be added either by humans into the descriptor files, or added + by automated processes when the entity is ingested into the catalog. +- Entities may have a number of _labels_ on them. +- Entities may have a number of _relations_, expressing how they relate to each + other in different ways. + +We'll list different possibilities for extending this below. + +## Adding a New apiVersion of an Existing Kind + +Example intents: + +> "I want to evolve this core kind, tweaking the semantics a bit so I will bump +> the apiVersion a step" + +> "This core kind is a decent fit but we want to evolve it at will so we'll move +> it to our own company's apiVersion space and use that instead of +> `backstage.io`." + +The `backstage.io` apiVersion space is reserved for use by the Backstage +maintainers. Please do not change or add versions within that space. + +If you add an [apiVersion](descriptor-format.md#apiversion-and-kind-required) +space of your own, you are effectively branching out from the underlying kind +and making your own. An entity kind is identified by the apiVersion + kind pair, +so even though the resulting entity may be similar to the core one, there will +be no guarantees that plugins will be able to parse or understand its data. See +below about adding a new kind. + +## Adding a New Kind + +Example intents: + +> "The kinds that come with the package are lacking. I want to model this other +> thing that is a poor fit for either of the builtins." + +> "This core kind is a decent fit but we want to evolve it at will so we'll move +> it to our own company's apiVersion space and use that instead of +> `backstage.io`." + +A [kind](descriptor-format.md#apiversion-and-kind-required) is an overarching +family, or an idea if you will, of entities that also share a schema. Backstage +comes with a number of builtin ones that we believe are useful for a large +variety of needs that one may want to model in Backstage. The primary ambition +is to map things to these kinds, but sometimes you may want or need to extend +beyond them. + +Introducing a new apiVersion is basically the same as adding a new kind. Bear in +mind that most plugins will be compiled against the builtin +`@backstage/catalog-model` package and have expectations that kinds align with +that. + +The catalog backend itself, from a storage and API standpoint, does not care +about the kind of entities it stores. Extending with new kinds is mainly a +matter of permitting them to pass validation when building the backend catalog +using the `CatalogBuilder`, and then to make plugins be able to understand the +new kind. + +For the consuming side, it's a different story. Adding a kind has a very large +impact. The very foundation of Backstage is to attach behavior and views and +functionality to entities that we ascribe some meaning to. There will be many +places where code checks `if (kind === 'X')` for some hard coded `X`, and casts +it to a concrete type that it imported from a package such as +`@backstage/catalog-model`. + +If you want to model something that doesn't feel like a fit for either of the +builtin kinds, feel free to reach out to the Backstage maintainers to discuss +how to best proceed. + +If you end up adding that new kind, you must namespace its `apiVersion` +accordingly with a prefix that makes sense, typically based on your organization +name - e.g. `my-company.net/v1`. Also do pick a new `kind` identifier that does +not collide with the builtin kinds. + +## Adding a New Type of an Existing Kind + +Example intents: + +> "This is clearly a component, but it's of a type that doesn't quite fit with +> the ones I've seen before." + +> "We don't call our teams "team", can't we put "flock" as the group type?" + +Some entity kinds have a `type` field in its spec. This is where an organization +are free to express the variety of entities within a kind. This field is +expected to follow some taxonomy that makes sense for yourself. The chosen value +may affect what operations and views are enabled in Backstage for that entity. +Inside Spotify our model has grown significantly over the years, and our +component types now include ML models, apps, data pipelines and many more. + +It might be tempting to put software that doesn't fit into any of the existing +types into an Other catch-all type. There are a few reasons why we advise +against this; firstly, we have found that it is preferred to match the +conceptual model that your engineers have when describing your software. +Secondly, Backstage helps your engineers manage their software by integrating +the infrastructure tooling through plugins. Different plugins are used for +managing different types of components. + +For example, the +[Lighthouse plugin](https://github.com/backstage/community-plugins/tree/main/workspaces/lighthouse/plugins/lighthouse) +only makes sense for Websites. The more specific you can be in how you model +your software, the easier it is to provide plugins that are contextual. + +Adding a new type takes relatively little effort and carries little risk. Any +type value is accepted by the catalog backend, but plugins may have to be +updated if you want particular behaviors attached to that new type. + +## Changing the Validation Rules for The Entity Envelope or Metadata Fields + +Example intents: + +> "We want to import our old catalog but the default set of allowed characters +> for a metadata.name are too strict." + +> "I want to change the rules for annotations so that I'm allowed to store any +> data in annotation values, not just strings." + +After pieces of raw entity data have been read from a location, they are passed +through a field format validation step. This ensures that the types and syntax +of the base envelope and metadata make sense - in short, things that aren't +entity-kind-specific. Some or all of these validators can be replaced when +building the backend using the catalog's dedicated `catalogModelExtensionPoint` +(or directly on the `CatalogBuilder` if you are still using the old backend +system). + +The risk and impact of this type of extension varies, based on what it is that +you want to do. For example, extending the valid character set for kinds, +namespaces and names can be fairly harmless, with a few notable exceptions - +there is code that expects these to never ever contain a colon or slash, for +example, and introducing URL-unsafe characters risks breaking plugins that +aren't careful about encoding arguments. Supporting non-strings in annotations +may be possible but has not yet been tried out in the real world - there is +likely to be some level of plugin breakage that can be hard to predict. + +You must also be careful about not making the rules _more strict_ than they used +to be after populating the catalog with data. This risks making previously valid +entities start having processing errors and fail to update. + +Before making this kind of extension, we recommend that you contact the +Backstage maintainers or a support partner to discuss your use case. + +This is an example of relaxing the format rules of the `metadata.name` field: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogModelExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; + +const myCatalogCustomizations = createBackendModule({ + pluginId: 'catalog', + moduleId: 'catalog-customization', + register(reg) { + reg.registerInit({ + deps: { + catalogModel: catalogModelExtensionPoint, + }, + async init({ catalogModel }) { + catalogModel.setFieldValidators({ + // This is only one of many methods that you can pass into + // setFieldValidators; your editor of choice should help you + // find the others. The length checks and regexp inside are + // just examples and can be adjusted as needed, but take care + // to test your changes thoroughly to ensure that you get + // them right. + isValidEntityName(value) { + return ( + typeof value === 'string' && + value.length >= 1 && + value.length <= 63 && + /^[A-Za-z0-9@+_.-]+$/.test(value) + ); + }, + }); + }, + }); + }, +}); + +const backend = createBackend(); +// ... add other backend features and the catalog backend itself here ... +backend.add(myCatalogCustomizations); +backend.start(); +``` + +## Changing the Validation Rules for Core Entity Fields + +Example intent: + +> "I don't like that the owner is mandatory. I'd like it to be optional." + +After reading and policy-checked entity data from a location, it is sent through +the processor chain looking for processors that implement the +`validateEntityKind` step, to see that the data is of a known kind and abides by +its schema. There is a builtin processor that implements this for all known core +kinds and matches the data against their fixed validation schema. This processor +can be replaced when building the backend catalog using the `CatalogBuilder`, +with a processor of your own that validates the data differently. +This replacement processor must have a name that matches the builtin processor, `BuiltinKindsEntityProcessor`. + +This type of extension is high risk, and may have high impact across the +ecosystem depending on the type of change that is made. It is therefore not +recommended in normal cases. There will be a large number of plugins and +processors - and even the core itself - that make assumptions about the shape of +the data and import the typescript data type from the `@backstage/catalog-model` +package. + +## Adding New Fields to the Metadata Object + +Example intent: + +> "Our entities have this auxiliary property that I would like to express for +> several entity kinds and it doesn't really fit as a spec field." + +The metadata object is currently left open for extension. Any unknown fields +found in the metadata will just be stored verbatim in the catalog. However we +want to caution against extending the metadata excessively. Firstly, you run the +risk of colliding with future extensions to the model. Secondly, it is common +that this type of extension lives more comfortably elsewhere - primarily in the +metadata labels or annotations, but sometimes you even may want to make a new +component type or similar instead. + +There are some situations where metadata can be the right place. If you feel +that you have run into such a case and that it would apply to others, do feel +free to contact the Backstage maintainers or a support partner to discuss your +use case. Maybe we can extend the core model to benefit both you and others. + +## Adding New Fields to the Spec Object of an Existing Kind + +Example intent: + +> "The builtin Component kind is fine but we want to add an additional field to +> the spec for describing whether it's in prod or staging." + +A kind's schema validation typically doesn't forbid "unknown" fields in an +entity `spec`, and the catalog will happily store whatever is in it. So doing +this will usually work from the catalog's point of view. + +Adding fields like this is subject to the same risks as mentioned about metadata +extensions above. Firstly, you run the risk of colliding with future extensions +to the model. Secondly, it is common that this type of extension lives more +comfortably elsewhere - primarily in the metadata labels or annotations, but +sometimes you even may want to make a new component type or similar instead. + +There are some situations where the spec can be the right place. If you feel +that you have run into such a case and that it would apply to others, do feel +free to contact the Backstage maintainers or a support partner to discuss your +use case. Maybe we can extend the core model to benefit both you and others. + +## Adding a New Annotation + +Example intents: + +> "Our custom made build system has the concept of a named pipeline-set, and we +> want to associate individual components with their corresponding pipeline-sets +> so we can show their build status." + +> "We have an alerting system that automatically monitors service health, and +> there's this integration key that binds the service to an alerts pool. We want +> to be able to show the ongoing alerts for our services in Backstage so it'd be +> nice to attach that integration key to the entity somehow." + +Annotations are mainly intended to be consumed by plugins, for feature detection +or linking into external systems. Sometimes they are added by humans, but often +they are automatically generated at ingestion time by processors. There is a set +of [well-known annotations](well-known-annotations.md), but you are free to add +additional ones. This carries no risk or impact to other systems as long as you +abide by the following naming rules. + +- The `backstage.io` annotation prefix is reserved for use by the Backstage + maintainers. Reach out to us if you feel that you would like to make an + addition to that prefix. +- Annotations that pertain to a well known third party system should ideally be + prefixed with a domain, in a way that makes sense to a reader and connects it + clearly to the system (or the maker of the system). For example, you might use + a `pagerduty.com` prefix for pagerduty related annotations, but maybe not + `ldap.com` for LDAP annotations since it's not directly affiliated with or + owned by an LDAP foundation/company/similar. +- Annotations that have no prefix at all, are considered local to your Backstage + instance and can be used freely as such, but you should not make use of them + outside of your organization. For example, if you were to open source a plugin + that generates or consumes annotations, then those annotations must be + properly prefixed with your company domain or a domain that pertains to the + annotation at hand. + +## Adding a New Label + +Example intents: + +> "Our process reaping system wants to periodically scrape for components that +> have a certain property." + +> "It'd be nice if our service owners could just tag their components somehow to +> let the CD system know to automatically generate SRV records or not for that +> service." + +Labels are mainly intended to be used for filtering of entities, by external +systems that want to find entities that have some certain property. This is +sometimes used for feature detection / selection. An example could be to add a +label `deployments.my-company.net/register-srv: "true"`. + +At the time of writing this, the use of labels is very limited and we are still +settling together with the community on how to best use them. If you feel that +your use case fits the labels best, we would appreciate if you let the Backstage +maintainers know. + +You are free to add labels. This carries no risk or impact to other systems as +long as you abide by the following naming rules. + +- The `backstage.io` label prefix is reserved for use by the Backstage + maintainers. Reach out to us if you feel that you would like to make an + addition to that prefix. +- Labels that pertain to a well known third party system should ideally be + prefixed with a domain, in a way that makes sense to a reader and connects it + clearly to the system (or the maker of the system). For example, you might use + a `pagerduty.com` prefix for pagerduty related labels, but maybe not + `ldap.com` for LDAP labels since it's not directly affiliated with or owned by + an LDAP foundation/company/similar. +- Labels that have no prefix at all, are considered local to your Backstage + instance and can be used freely as such, but you should not make use of them + outside of your organization. For example, if you were to open source a plugin + that generates or consumes labels, then those labels must be properly prefixed + with your company domain or a domain that pertains to the label at hand. + +## Adding a New Relation Type + +Example intents: + +> "We have this concept of service maintainership, separate from ownership, that +> we would like to make relations to individual users for." + +> "We feel that we want to explicitly model the team-to-global-department +> mapping as a relation, because it is core to our org setup and we frequently +> query for it." + +Any processor can emit relations for entities as they are being processed, and +new processors can be added when building the backend catalog using the +`CatalogBuilder`. They can emit relations based on the entity data itself, or +based on information gathered from elsewhere. Relations are directed and go from +a source entity to a target entity. They are also tied to the entity that +originated them - the one that was subject to processing when the relation was +emitted. Relations may be dangling (referencing something that does not actually +exist by that name in the catalog), and callers need to be aware of that. + +There is a set of [well-known relations](well-known-relations.md), but you are +free to emit your own as well. You cannot change the fact that they are directed +and have a source and target that have to be an +[entity reference](references.md), but you can invent your own types. You do not +have to make any changes to the catalog backend in order to accept new relation +types. + +At the time of writing this, we do not have any namespacing/prefixing scheme for +relation types. The type is also not validated to contain only some particular +set of characters. Until rules for this are settled, you should stick to using +only letters, dashes and digits, and to avoid collisions with future core +relation types, you may want to prefix the type somehow. For example: +`myCompany-maintainerOf` + `myCompany-maintainedBy`. + +If you have a suggestion for a relation type to be elevated to the core +offering, reach out to the Backstage maintainers or a support partner. + +## Using a Well-Known Relation Type for a New Purpose + +Example intents: + +> "The ownerOf/ownedBy relation types sound like a good fit for expressing how +> users are technical owners of our company specific ServiceAccount kind, and we +> want to reuse those relation types for that." + +At the time of writing, this is uncharted territory. If the documented use of a +relation states that one end of the relation commonly is a User or a Group, for +example, then consumers are likely to have conditional statements on the form +`if (x.kind === 'User') {} else {}`, which get confused when an unexpected kind +appears. + +If you want to extend the use of an established relation type in a way that has +an effect outside of your organization, reach out to the Backstage maintainers +or a support partner to discuss risk/impact. It may even be that one end of the +relation could be considered for addition to the core. + +## Adding a New Status field + +Example intent: + +> "We would like to convey entity statuses through the catalog in a generic way, +> as an integration layer. Our monitoring and alerting system has a plugin with +> Backstage, and it would be useful if the entity's status field contained the +> current alert state close to the actual entity data for anyone to consume. We +> find the `status.items` semantics a poor fit, so we would prefer to make our +> own custom field under `status` for these purposes." + +We have not yet ventured to define any generic semantics for the `status` +object. We recommend sticking with the `status.items` mechanism where possible +(see below), since third party consumers will not be able to consume your status +information otherwise. Please reach out to the maintainers on Discord or by +making a GitHub issue describing your use case if you are interested in this +topic. + +## Adding a New Status Item Type + +Example intent: + +> "The semantics of the entity `status.items` field are fine for our needs, but +> we want to contribute our own type of status into that array instead of the +> catalog specific one." + +This is a simple, low risk way of adding your own status information to +entities. Consumers will be able to easily track and display the status together +with other types / sources. + +We recommend that any status type that are not strictly private within the +organization be namespaced to avoid collisions. Statuses emitted by Backstage +core processes will for example be prefixed with `backstage.io/`, your +organization may prefix with `my-org.net/`, and `pagerduty.com/active-alerts` +could be a sensible complete status item type for that particular external +system. + +The mechanics for how to emit custom statuses is not in place yet, so if this is +of interest to you, you might consider contacting the maintainers on Discord or +my making a GitHub issue describing your use case. +[This issue](https://github.com/backstage/backstage/issues/2292) also contains +more context. + +## Referencing different environments with the model + +Example intent: + +> "I have multiple versions of my API deployed in different environments so I +> want to have `mytool-dev` and `mytool-prod` as different entities." + +While it's possible to have different versions of the same thing represented as +separate entities, it's something we generally recommend against. We believe +that a developer should be able to just find for example one `Component` +representing a service, and to be able to see the different code versions that +are deployed throughout your stack within its view. This reasoning works +similarly for other kinds as well, such as `API`. + +That being said - sometimes the differences between versions are so large, that +they represent what is for all intents and purposes an entirely new entity as +seen from the consumer's point of view. This can happen for example for +different _significant_ major versions of an API, and in particular if the two +major versions coexist in the ecosystem for some time. In those cases, it can be +motivated to have one `my-api-v2` and one `my-api-v3` named entity. This matches +the end user's expectations when searching for the API, and matches the desire +to maybe have separate documentation for the two and similar. But use this +sparingly - only do it if the extra modelling burden is outweighed by any +potential better clarity for users. + +When writing your custom plugins, we encourage designing them such that they can +show all the different variations through environments etc under one canonical +reference to your software in the catalog. For example for a continuous +deployment plugin, a user is likely to be greatly helped by being able to see +the entity's versions deployed in all different environments next to each other +in one view. That is also where they might be offered the ability to promote +from one environment to the other, do rollbacks, see their relative performance +metrics, and similar. This coherency and collection of tooling in one place is +where something like Backstage can offer the most value and effectiveness of +use. Splitting your entities apart into small islands makes this harder. + +## Implementing custom model extensions + +This section walks you through the steps involved extending the catalog model +with a new Entity type. + +### Creating a custom entity definition + +The first step of introducing a custom entity is to define what shape and schema +it has. We do this using a TypeScript type, as well as a JSONSchema schema. + +Most of the time you will want to have at least the TypeScript type of your +extension available in both frontend and backend code, which means you likely +want to have an isomorphic package that houses these types. Within the Backstage +main repo the package naming pattern of `-common` is used for isomorphic +packages, and you may choose to adopt this pattern as well. + +You can generate an isomorphic plugin package by running:`yarn new --select plugin-common` +or you can run `yarn new` and then select "plugin-common" from the list of options + +There's at this point no existing templates for generating isomorphic plugins +using the `@backstage/cli`. Perhaps the simplest way to get started right now is +to copy the contents of one of the existing packages in the main repository, +such as `plugins/scaffolder-common`, and rename the folder and file contents to +the desired name. This example uses _foobar_ as the plugin name so the plugin +will be named _foobar-common_. + +Once you have a common package in place you can start adding your own entity +definitions. For the exact details on how to do that we defer to getting +inspired by the existing +[scaffolder-common](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-common/src/index.ts) +package. But in short you will need to declare a TypeScript type and a +JSONSchema for the new entity kind. + +### Building a custom processor for the entity + +The next step is to create a custom processor for your new entity kind. This +will be used within the catalog to make sure that it's able to ingest and +validate entities of our new kind. Just like with the definition package, you +can find inspiration in for example the existing +[ScaffolderEntitiesProcessor](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-scaffolder-entity-model/src/processor/ScaffolderEntitiesProcessor.ts). +We also provide a high-level example of what a catalog process for a custom +entity might look like: + +```ts +import { CatalogProcessor, CatalogProcessorEmit, processingResult } from '@backstage/plugin-catalog-node'; +import { LocationSpec } from '@backstage/plugin-catalog-common' +import { Entity, entityKindSchemaValidator } from '@backstage/catalog-model'; + +// For an example of the JSONSchema format and how to use $ref markers to the +// base definitions, see: +// https://github.com/backstage/backstage/tree/master/packages/catalog-model/src/schema/kinds/Component.v1alpha1.schema.json +import { foobarEntityV1alpha1Schema } from '@internal/catalog-model'; + +export class FoobarEntitiesProcessor implements CatalogProcessor { + // You often end up wanting to support multiple versions of your kind as you + // iterate on the definition, so we keep each version inside this array as a + // convenient pattern. + private readonly validators = [ + // This is where we use the JSONSchema that we export from our isomorphic + // package + entityKindSchemaValidator(foobarEntityV1alpha1Schema), + ]; + + // Return processor name + getProcessorName(): string { + return 'FoobarEntitiesProcessor' + } + + // validateEntityKind is responsible for signaling to the catalog processing + // engine that this entity is valid and should therefore be submitted for + // further processing. + async validateEntityKind(entity: Entity): Promise { + for (const validator of this.validators) { + // If the validator throws an exception, the entity will be marked as + // invalid. + if (validator(entity)) { + return true; + } + } + + // Returning false signals that we don't know what this is, passing the + // responsibility to other processors to try to validate it instead. + return false; + } + + async postProcessEntity( + entity: Entity, + _location: LocationSpec, + emit: CatalogProcessorEmit, + ): Promise { + if ( + entity.apiVersion === 'example.com/v1alpha1' && + entity.kind === 'Foobar' + ) { + const foobarEntity = entity as FoobarEntityV1alpha1; + + // Typically you will want to emit any relations associated with the + // entity here. + emit(processingResult.relation({ ... })) + } + + return entity; + } +} +``` + +Once the processor is created it can be wired up to the catalog via the +`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + /* highlight-add-next-line */ + builder.addProcessor(new FoobarEntitiesProcessor()); + const { processingEngine, router } = await builder.build(); + // .. +} +``` diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index ee363819cd..4625e81b13 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -519,6 +519,9 @@ will be used within the catalog to make sure that it's able to ingest and validate entities of our new kind. Just like with the definition package, you can find inspiration in for example the existing [ScaffolderEntitiesProcessor](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-scaffolder-entity-model/src/processor/ScaffolderEntitiesProcessor.ts). + +The custom processor should be created as a separate module for the catalog plugin. For information on how to set that up, see the [plugin docs](../../plugins/backend-plugin.md#creating-a-backend-plugin). Use `yarn new --select backend-module` instead to create a module. For our case, the module ID will be `foobar` and the plugin ID will be `catalog`. + We also provide a high-level example of what a catalog process for a custom entity might look like: @@ -585,9 +588,9 @@ export class FoobarEntitiesProcessor implements CatalogProcessor { } ``` -### New Backend +#### New Backend -You should generally create a new module to hold your new processor. You can create a new backend module using the `backstage-cli create` command and selecting `backend-module` option. To create a new module, you need a plugin ID and a module ID. We'll be using `catalog` as our plugin ID since our module is adding/updating catalog functionality. For module ID, we'll use `foobar`, but this should match the ID of whatever your plugin that's integrating with the catalog is, for example, AWS would be `aws`, Backstage Search would be `search`, etc. +To them use your custom processor, you'll need to add the module to your backend as well as integrate your module with the catalog plugin. ```ts title="plugins/catalog-backend-module-foobar/src/index.ts" import { @@ -616,28 +619,12 @@ export const catalogModuleFoobarEntitiesProcessor = createBackendModule({ export default catalogModuleFoobarEntitiesProcessor; ``` -that can then be installed to your backend as a regular module, like so, +This module can then be installed to your backend like so, ```ts backend.add(import('@internal/plugin-catalog-backend-module-foobar')); ``` -### Old Backend +#### Legacy Backend -Once the processor is created it can be wired up to the catalog via the -`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-next-line */ - builder.addProcessor(new FoobarEntitiesProcessor()); - const { processingEngine, router } = await builder.build(); - // .. -} -``` +Look through the [legacy documentation](./extending-the-model--old.md). From 82c91d5f6b08f0b907ad9aa05d72191ed3dd412e Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 6 May 2024 19:06:51 -0400 Subject: [PATCH 272/567] update id Signed-off-by: aramissennyeydd --- docs/features/software-catalog/extending-the-model--old.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/extending-the-model--old.md b/docs/features/software-catalog/extending-the-model--old.md index b3d7de3a65..7368a4a339 100644 --- a/docs/features/software-catalog/extending-the-model--old.md +++ b/docs/features/software-catalog/extending-the-model--old.md @@ -1,5 +1,5 @@ --- -id: extending-the-model +id: extending-the-model--old title: Extending the model # prettier-ignore description: Documentation on extending the catalog model From a502dc2c63d03033fc8dec97fd16225d6d62310d Mon Sep 17 00:00:00 2001 From: Ishwarya Surendrababu Date: Tue, 7 May 2024 13:53:56 +0530 Subject: [PATCH 273/567] Updated the title and renamed the file and image Signed-off-by: Ishwarya Surendrababu --- .../data/plugins/{deploy.yaml => digital.ai-deploy.yaml} | 4 ++-- microsite/static/img/{deploy.svg => digital.ai-deploy.svg} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename microsite/data/plugins/{deploy.yaml => digital.ai-deploy.yaml} (87%) rename microsite/static/img/{deploy.svg => digital.ai-deploy.svg} (100%) diff --git a/microsite/data/plugins/deploy.yaml b/microsite/data/plugins/digital.ai-deploy.yaml similarity index 87% rename from microsite/data/plugins/deploy.yaml rename to microsite/data/plugins/digital.ai-deploy.yaml index cd96e4b60e..3c45330449 100644 --- a/microsite/data/plugins/deploy.yaml +++ b/microsite/data/plugins/digital.ai-deploy.yaml @@ -1,11 +1,11 @@ --- -title: Deploy +title: digital.ai Deploy author: digital.ai authorUrl: https://digital.ai/ category: CI/CD description: The plugin offers integration with Digital.ai Deploy and backstage components and services. It provide access to deployments and reports. documentation: https://docs.digital.ai/bundle/devops-deploy-version-v.24.1/page/deploy/concept/xl-deploy-backstage-overview.html -iconUrl: /img/deploy.svg +iconUrl: /img/digital-ai-deploy.svg npmPackageName: '@digital.ai/plugin-dai-deploy' tags: - ci diff --git a/microsite/static/img/deploy.svg b/microsite/static/img/digital.ai-deploy.svg similarity index 100% rename from microsite/static/img/deploy.svg rename to microsite/static/img/digital.ai-deploy.svg From c5d7b40b4b40da3340e5327445d32bd4753bd55e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 28 Feb 2024 13:30:52 +0100 Subject: [PATCH 274/567] fix the opentelemetry setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/sharp-glasses-live.md | 5 + docs/tutorials/setup-opentelemetry.md | 74 +- packages/backend-legacy/package.json | 3 - packages/backend-legacy/src/index.ts | 11 - packages/backend/package.json | 10 +- .../prometheus.yml | 0 packages/backend/src/instrumentation.js | 34 + packages/cli/cli-report.md | 1 + packages/cli/src/commands/index.ts | 1 + packages/cli/src/commands/start/command.ts | 1 + .../cli/src/commands/start/startBackend.ts | 6 + packages/cli/src/lib/bundler/config.ts | 3 + packages/cli/src/lib/bundler/types.ts | 2 + .../experimental/startBackendExperimental.ts | 3 + yarn.lock | 1621 ++++++++++++++++- 15 files changed, 1687 insertions(+), 88 deletions(-) create mode 100644 .changeset/sharp-glasses-live.md rename packages/{backend-legacy => backend}/prometheus.yml (100%) create mode 100644 packages/backend/src/instrumentation.js diff --git a/.changeset/sharp-glasses-live.md b/.changeset/sharp-glasses-live.md new file mode 100644 index 0000000000..000f49fc47 --- /dev/null +++ b/.changeset/sharp-glasses-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Allow passing a `--require` argument through to the Node process during `package start` diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md index 976ba4ccfb..c49eef26a2 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -6,42 +6,37 @@ description: Tutorial to setup OpenTelemetry metrics and traces exporters in Bac Backstage uses [OpenTelemetery](https://opentelemetry.io/) to instrument its components by reporting traces and metrics. -This tutorial shows how to setup exporters in your Backstage backend package. For demonstration purposes we will use the simple console exporters. +This tutorial shows how to setup exporters in your Backstage backend package. For demonstration purposes we will use a Prometheus exporter, but you can adjust your solution to use another one that suits your needs; see for example the article on [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/). ## Install dependencies We will use the OpenTelemetry Node SDK and the `auto-instrumentations-node` packages. -Backstage packages, such as the catalog, uses the OpenTelemetry API to send custom traces and metrics. +Backstage packages, such as the catalog, use the OpenTelemetry API to send custom traces and metrics. The `auto-instrumentations-node` will automatically create spans for code called in libraries like Express. ```bash -yarn --cwd packages/backend add @opentelemetry/sdk-node \ +yarn --cwd packages/backend add \ + @opentelemetry/sdk-node \ @opentelemetry/auto-instrumentations-node \ - @opentelemetry/sdk-metrics \ - @opentelemetry/sdk-trace-node + @opentelemetry/exporter-prometheus ``` ## Configure -In your `packages/backend` folder, create an `instrumentation.js` file. +In your `packages/backend/src` folder, create an `instrumentation.js` file. -```typescript +```typescript title="in packages/backend/src/instrumentation.js" const { NodeSDK } = require('@opentelemetry/sdk-node'); -const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-node'); const { getNodeAutoInstrumentations, } = require('@opentelemetry/auto-instrumentations-node'); -const { - PeriodicExportingMetricReader, - ConsoleMetricExporter, -} = require('@opentelemetry/sdk-metrics'); +const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); +const prometheus = new PrometheusExporter(); const sdk = new NodeSDK({ - traceExporter: new ConsoleSpanExporter(), - metricReader: new PeriodicExportingMetricReader({ - exporter: new ConsoleMetricExporter(), - }), + // You can add a traceExporter field here too + metricReader: prometheus, instrumentations: [getNodeAutoInstrumentations()], }); @@ -51,42 +46,33 @@ sdk.start(); You probably won't need all of the instrumentation inside `getNodeAutoInstrumentations()` so make sure to check the [documentation](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node) and tweak it properly. -It's important to setup the NodeSDK and the automatic instrumentation **before** importing any library. +## Local Development Setup -This is why we will use the nodejs [`--require`](https://nodejs.org/api/cli.html#-r---require-module) -flag when we start up the application. +It's important to setup the NodeSDK and the automatic instrumentation **before** +importing any library. This is why we will use the nodejs +[`--require`](https://nodejs.org/api/cli.html#-r---require-module) flag when we +start up the application. -In your `Dockerfile` add the `--require` flag which points to the `instrumentation.js` file +For local development, you can add the required flag in your `packages/backend/package.json`. -```Dockerfile - -# We need the instrumentation file inside the Docker image so we can use it with --require -// highlight-add-next-line -COPY --chown=node:node packages/backend/instrumentation.js ./ - -// highlight-remove-next-line -CMD ["node", "packages/backend", "--config", "app-config.yaml"] -// highlight-add-next-line -CMD ["node", "--require", "./instrumentation.js", "packages/backend", "--config", "app-config.yaml"] -``` - -## Run Backstage - -The above configuration will only work in production once your start a Docker container from the image. - -To be able to test locally you can import the `./instrumentation.js` file at the top (before all imports) of your backend `index.ts` file - -```ts -import '../instrumentation.js' -// Other imports -... +```json title="packages/backend/package.json" +"scripts": { + "start": "backstage-cli package start --require ./src/instrumentation.js", + ... ``` You can now start your Backstage instance as usual, using `yarn dev`. -When the backend is started, you should see in your console traces and metrics emitted by OpenTelemetry. +## Production Setup -Of course in production you probably won't use the console exporters but instead send traces and metrics to an OpenTelemetry Collector or other exporter using [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/). +In your `Dockerfile` add the `--require` flag which points to the `instrumentation.js` file + +```Dockerfile +// highlight-remove-next-line +CMD ["node", "packages/backend", "--config", "app-config.yaml"] +// highlight-add-next-line +CMD ["node", "--require", "./src/instrumentation.js", "packages/backend", "--config", "app-config.yaml"] +``` If you need to disable/configure some OpenTelemetry feature there are lots of [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) which you can tweak. diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index 97178dcb2c..47c04f9a3c 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -65,9 +65,6 @@ "@backstage/plugin-techdocs-backend": "workspace:^", "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^19.0.3", - "@opentelemetry/api": "^1.4.1", - "@opentelemetry/exporter-prometheus": "^0.50.0", - "@opentelemetry/sdk-metrics": "^1.13.0", "azure-devops-node-api": "^12.0.0", "better-sqlite3": "^9.0.0", "dockerode": "^4.0.0", diff --git a/packages/backend-legacy/src/index.ts b/packages/backend-legacy/src/index.ts index 34ea8b0f6c..33520cba03 100644 --- a/packages/backend-legacy/src/index.ts +++ b/packages/backend-legacy/src/index.ts @@ -56,19 +56,8 @@ import { ServerPermissionClient } from '@backstage/plugin-permission-node'; import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; import { DefaultEventBroker } from '@backstage/plugin-events-backend'; import { DefaultEventsService } from '@backstage/plugin-events-node'; -import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; -import { MeterProvider } from '@opentelemetry/sdk-metrics'; -import { metrics } from '@opentelemetry/api'; import { DefaultSignalsService } from '@backstage/plugin-signals-node'; -// Expose opentelemetry metrics using a Prometheus exporter on -// http://localhost:9464/metrics . See prometheus.yml in packages/backend for -// more information on how to scrape it. -const exporter = new PrometheusExporter(); -const meterProvider = new MeterProvider(); -metrics.setGlobalMeterProvider(meterProvider); -meterProvider.addMetricReader(exporter); - function makeCreateEnv(config: Config) { const root = getRootLogger(); const reader = UrlReaders.default({ logger: root, config }); diff --git a/packages/backend/package.json b/packages/backend/package.json index c76a3be0a3..8d59d52ea8 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -21,9 +21,10 @@ "build": "backstage-cli package build", "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "start": "backstage-cli package start", + "start": "backstage-cli package start --require ./src/instrumentation.js", "test": "backstage-cli package test", - "build-image": "docker build ../.. -f Dockerfile --tag example-backend" + "build-image": "docker build ../.. -f Dockerfile --tag example-backend", + "start:prometheus": "docker run --mount type=bind,source=./prometheus.yml,destination=/etc/prometheus/prometheus.yml --publish published=9090,target=9090,protocol=tcp prom/prometheus" }, "dependencies": { "@backstage/backend-defaults": "workspace:^", @@ -56,7 +57,10 @@ "@backstage/plugin-search-backend-module-techdocs": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-signals-backend": "workspace:^", - "@backstage/plugin-techdocs-backend": "workspace:^" + "@backstage/plugin-techdocs-backend": "workspace:^", + "@opentelemetry/auto-instrumentations-node": "^0.43.0", + "@opentelemetry/exporter-prometheus": "^0.50.0", + "@opentelemetry/sdk-node": "^0.50.0" }, "devDependencies": { "@backstage/cli": "workspace:^" diff --git a/packages/backend-legacy/prometheus.yml b/packages/backend/prometheus.yml similarity index 100% rename from packages/backend-legacy/prometheus.yml rename to packages/backend/prometheus.yml diff --git a/packages/backend/src/instrumentation.js b/packages/backend/src/instrumentation.js new file mode 100644 index 0000000000..e3725632c1 --- /dev/null +++ b/packages/backend/src/instrumentation.js @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { NodeSDK } = require('@opentelemetry/sdk-node'); +const { + getNodeAutoInstrumentations, +} = require('@opentelemetry/auto-instrumentations-node'); +const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); + +// Expose opentelemetry metrics using a Prometheus exporter on +// http://localhost:9464/metrics. See packages/backend/prometheus.yml for +// more information on how to scrape it. +const prometheus = new PrometheusExporter(); + +const sdk = new NodeSDK({ + // traceExporter: ..., + metricReader: prometheus, + instrumentations: [getNodeAutoInstrumentations()], +}); + +sdk.start(); diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 819b1a2261..7b6df4366c 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -280,6 +280,7 @@ Options: --check --inspect [host] --inspect-brk [host] + --require -h, --help ``` diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index fe23926148..3554ddfbef 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -120,6 +120,7 @@ export function registerScriptCommand(program: Command) { '--inspect-brk [host]', 'Enable debugger in Node.js environments, breaking before code starts', ) + .option('--require ', 'Add a --require argument to the node process') .action(lazy(() => import('./start').then(m => m.command))); command diff --git a/packages/cli/src/commands/start/command.ts b/packages/cli/src/commands/start/command.ts index 427f8b6efb..2f2af95d85 100644 --- a/packages/cli/src/commands/start/command.ts +++ b/packages/cli/src/commands/start/command.ts @@ -27,6 +27,7 @@ export async function command(opts: OptionValues): Promise { checksEnabled: Boolean(opts.check), inspectEnabled: opts.inspect, inspectBrkEnabled: opts.inspectBrk, + require: opts.require, }; switch (role) { diff --git a/packages/cli/src/commands/start/startBackend.ts b/packages/cli/src/commands/start/startBackend.ts index 25758a0939..778a31e7e7 100644 --- a/packages/cli/src/commands/start/startBackend.ts +++ b/packages/cli/src/commands/start/startBackend.ts @@ -23,6 +23,7 @@ interface StartBackendOptions { checksEnabled: boolean; inspectEnabled: boolean; inspectBrkEnabled: boolean; + require?: string; } export async function startBackend(options: StartBackendOptions) { @@ -32,6 +33,7 @@ export async function startBackend(options: StartBackendOptions) { checksEnabled: false, // not supported inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled, + require: options.require, }); await waitForExit(); @@ -41,6 +43,7 @@ export async function startBackend(options: StartBackendOptions) { checksEnabled: options.checksEnabled, inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled, + require: options.require, }); await waitForExit(); @@ -70,6 +73,7 @@ export async function startBackendPlugin(options: StartBackendOptions) { checksEnabled: false, // not supported inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled, + require: options.require, }); await waitForExit(); @@ -87,6 +91,7 @@ export async function startBackendPlugin(options: StartBackendOptions) { checksEnabled: options.checksEnabled, inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled, + require: options.require, }); await waitForExit(); @@ -98,6 +103,7 @@ async function cleanDistAndServeBackend(options: { checksEnabled: boolean; inspectEnabled: boolean; inspectBrkEnabled: boolean; + require?: string; }) { // Cleaning dist/ before we start the dev process helps work around an issue // where we end up with the entrypoint executing multiple times, causing diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index dc90bed1e7..53db7f3597 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -276,6 +276,9 @@ export async function createBackendConfig( : '--inspect-brk'; runScriptNodeArgs.push(inspect); } + if (options.require) { + runScriptNodeArgs.push(`--require=${options.require}`); + } return { mode: isDev ? 'development' : 'production', diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 8fe25e91c5..85a641465f 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -54,10 +54,12 @@ export type BackendBundlingOptions = { parallelism?: number; inspectEnabled: boolean; inspectBrkEnabled: boolean; + require?: string; }; export type BackendServeOptions = BundlingPathsOptions & { checksEnabled: boolean; inspectEnabled: boolean; inspectBrkEnabled: boolean; + require?: string; }; diff --git a/packages/cli/src/lib/experimental/startBackendExperimental.ts b/packages/cli/src/lib/experimental/startBackendExperimental.ts index 19f71aa993..bd4bb94b17 100644 --- a/packages/cli/src/lib/experimental/startBackendExperimental.ts +++ b/packages/cli/src/lib/experimental/startBackendExperimental.ts @@ -96,6 +96,9 @@ export async function startBackendExperimental(options: BackendServeOptions) { : '--inspect-brk'; optionArgs.push(inspect); } + if (options.require) { + optionArgs.push(`--require=${options.require}`); + } const userArgs = process.argv .slice(['node', 'backstage-cli', 'package', 'start'].length) diff --git a/yarn.lock b/yarn.lock index 89d4b0ee45..14d064088b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9362,6 +9362,16 @@ __metadata: languageName: node linkType: hard +"@grpc/grpc-js@npm:^1.7.1": + version: 1.10.7 + resolution: "@grpc/grpc-js@npm:1.10.7" + dependencies: + "@grpc/proto-loader": ^0.7.13 + "@js-sdsl/ordered-map": ^4.4.2 + checksum: 69e88768e59b53ca020e2cfa9474fbd645f4ee7dd3269559c9fb91970273da6e8db480c0c439bdd73b49f1831d8f47c9bc5305dc5f9ed4db8873d53572e4f019 + languageName: node + linkType: hard + "@grpc/grpc-js@npm:~1.9.6": version: 1.9.11 resolution: "@grpc/grpc-js@npm:1.9.11" @@ -9372,17 +9382,108 @@ __metadata: languageName: node linkType: hard -"@grpc/proto-loader@npm:^0.7.0, @grpc/proto-loader@npm:^0.7.8": - version: 0.7.10 - resolution: "@grpc/proto-loader@npm:0.7.10" +"@grpc/proto-loader@npm:^0.7.0, @grpc/proto-loader@npm:^0.7.13, @grpc/proto-loader@npm:^0.7.8": + version: 0.7.13 + resolution: "@grpc/proto-loader@npm:0.7.13" dependencies: lodash.camelcase: ^4.3.0 long: ^5.0.0 - protobufjs: ^7.2.4 + protobufjs: ^7.2.5 yargs: ^17.7.2 bin: proto-loader-gen-types: build/bin/proto-loader-gen-types.js - checksum: 4987e23b57942c2363b6a6a106e63efae636666cefa348778dfafef2ff72da7343c8587667521cb1d52482827bcd001dd535bdc27065110af56d9c7c176334c9 + checksum: 399c1b8a4627f93dc31660d9636ea6bf58be5675cc7581e3df56a249369e5be02c6cd0d642c5332b0d5673bc8621619bc06fb045aa3e8f57383737b5d35930dc + languageName: node + linkType: hard + +"@hapi/b64@npm:5.x.x": + version: 5.0.0 + resolution: "@hapi/b64@npm:5.0.0" + dependencies: + "@hapi/hoek": 9.x.x + checksum: 1e166bc9a6ca2952190ede40089d552efa21554c3325d5174e5616b940f79cd8327520b239ef6725f823f95b9e4684579bc8e99a222b28639b793ae0ef788409 + languageName: node + linkType: hard + +"@hapi/boom@npm:9.x.x, @hapi/boom@npm:^9.0.0": + version: 9.1.4 + resolution: "@hapi/boom@npm:9.1.4" + dependencies: + "@hapi/hoek": 9.x.x + checksum: b1cdde1e82fae8222d893ac74e13e9a784f0398ffcb7ece32f6eb69bad990ca62f3c40cca19673e74cc676628ff121ee5576d6b0f1add92dcfa182ff9b90b937 + languageName: node + linkType: hard + +"@hapi/bourne@npm:2.x.x": + version: 2.1.0 + resolution: "@hapi/bourne@npm:2.1.0" + checksum: 0ce5a38bc46b1b649fc04c00763def978c99b2eba5013e512f492f4d0d806a6fc1d09f36524c2f8b45cc778d481a06c1f808392e08bc6ebd14abab4bfde07ca5 + languageName: node + linkType: hard + +"@hapi/cryptiles@npm:5.x.x": + version: 5.1.0 + resolution: "@hapi/cryptiles@npm:5.1.0" + dependencies: + "@hapi/boom": 9.x.x + checksum: 3109ad8435d6333b22092e8264e0cc32baafaa10c8c813685ca379c033b5d4123cd503aecdb535fb0c2d39d8e26c494f4c4998d5d040865907b64bf4cb72c705 + languageName: node + linkType: hard + +"@hapi/hoek@npm:9.x.x, @hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": + version: 9.3.0 + resolution: "@hapi/hoek@npm:9.3.0" + checksum: 4771c7a776242c3c022b168046af4e324d116a9d2e1d60631ee64f474c6e38d1bb07092d898bf95c7bc5d334c5582798a1456321b2e53ca817d4e7c88bc25b43 + languageName: node + linkType: hard + +"@hapi/iron@npm:^6.0.0": + version: 6.0.0 + resolution: "@hapi/iron@npm:6.0.0" + dependencies: + "@hapi/b64": 5.x.x + "@hapi/boom": 9.x.x + "@hapi/bourne": 2.x.x + "@hapi/cryptiles": 5.x.x + "@hapi/hoek": 9.x.x + checksum: ef07abc8a55eb8b60ab0c09d797bb13b39d283260ecdabedc1568c64c47d8c15fe517beed4f76a2b69dac57e6c26cd30ac7612169c41adb8f4c77ea3f58d973d + languageName: node + linkType: hard + +"@hapi/podium@npm:^4.1.3": + version: 4.1.3 + resolution: "@hapi/podium@npm:4.1.3" + dependencies: + "@hapi/hoek": 9.x.x + "@hapi/teamwork": 5.x.x + "@hapi/validate": 1.x.x + checksum: da7d02af93a2797fc522cca0ec6cf12691a75047857db80162405d7f83bbf437d49f95c20714bd8e19f2ff41b8e5139e88fb7a896f5d967e0d9bcbf632a9feae + languageName: node + linkType: hard + +"@hapi/teamwork@npm:5.x.x": + version: 5.1.1 + resolution: "@hapi/teamwork@npm:5.1.1" + checksum: f679aff66b432f5fe3daa72a0659c4280de8f6e109e0c547ed24e7ea60149b182c406c4c02426a8bcfd87a79889b180f6d5f5a95690489e5607cc044c3c2defb + languageName: node + linkType: hard + +"@hapi/topo@npm:^5.0.0, @hapi/topo@npm:^5.1.0": + version: 5.1.0 + resolution: "@hapi/topo@npm:5.1.0" + dependencies: + "@hapi/hoek": ^9.0.0 + checksum: 604dfd5dde76d5c334bd03f9001fce69c7ce529883acf92da96f4fe7e51221bf5e5110e964caca287a6a616ba027c071748ab636ff178ad750547fba611d6014 + languageName: node + linkType: hard + +"@hapi/validate@npm:1.x.x": + version: 1.1.3 + resolution: "@hapi/validate@npm:1.1.3" + dependencies: + "@hapi/hoek": ^9.0.0 + "@hapi/topo": ^5.0.0 + checksum: dd6f8d6e33ac55d430448bc83c33572a593702ae856186610161a9488a611110ac4f793339043ea44a6f79bebe689bc7f86122df2f817725255159a0c1cb62ec languageName: node linkType: hard @@ -9931,6 +10032,13 @@ __metadata: languageName: node linkType: hard +"@js-sdsl/ordered-map@npm:^4.4.2": + version: 4.4.2 + resolution: "@js-sdsl/ordered-map@npm:4.4.2" + checksum: a927ae4ff8565ecb75355cc6886a4f8fadbf2af1268143c96c0cce3ba01261d241c3f4ba77f21f3f017a00f91dfe9e0673e95f830255945c80a0e96c6d30508a + languageName: node + linkType: hard + "@jsdevtools/ono@npm:7.1.3, @jsdevtools/ono@npm:^7.1.3": version: 7.1.3 resolution: "@jsdevtools/ono@npm:7.1.3" @@ -11908,13 +12016,114 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api@npm:^1.0.1, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.4.0, @opentelemetry/api@npm:^1.4.1": +"@opentelemetry/api-logs@npm:0.49.1, @opentelemetry/api-logs@npm:^0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/api-logs@npm:0.49.1" + dependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 83f559164fb62ed4343e650afccae766bb7ec730540a14f391d6ab4516a96a11cb1f9db9fc77495d07cb95541b4e8ccd184a6f36a0e38ddaad4fdd6359b3a3d9 + languageName: node + linkType: hard + +"@opentelemetry/api-logs@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/api-logs@npm:0.50.0" + dependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 5d4d9d448d1dc3a74879a19d5d24b9aecfd180e05acc622e25e5ca1bd0ad2c27b5541e101e474f2870e6470e148a7bad3c1b041d5a41181ebcde1f38a1ee6feb + languageName: node + linkType: hard + +"@opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.0.1, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.4.0": version: 1.8.0 resolution: "@opentelemetry/api@npm:1.8.0" checksum: 0e32079975f05bee6de2ad8ade097f0afdc63f462c76550150fce2444c73ab92aaf851ac85e638b6e3b269da6640ac7e63f33913a0fd7df9f9beec2e100759df languageName: node linkType: hard +"@opentelemetry/auto-instrumentations-node@npm:^0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/auto-instrumentations-node@npm:0.43.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/instrumentation-amqplib": ^0.35.0 + "@opentelemetry/instrumentation-aws-lambda": ^0.39.0 + "@opentelemetry/instrumentation-aws-sdk": ^0.39.1 + "@opentelemetry/instrumentation-bunyan": ^0.36.0 + "@opentelemetry/instrumentation-cassandra-driver": ^0.36.0 + "@opentelemetry/instrumentation-connect": ^0.34.0 + "@opentelemetry/instrumentation-cucumber": ^0.4.0 + "@opentelemetry/instrumentation-dataloader": ^0.7.0 + "@opentelemetry/instrumentation-dns": ^0.34.0 + "@opentelemetry/instrumentation-express": ^0.36.1 + "@opentelemetry/instrumentation-fastify": ^0.34.0 + "@opentelemetry/instrumentation-fs": ^0.10.0 + "@opentelemetry/instrumentation-generic-pool": ^0.34.0 + "@opentelemetry/instrumentation-graphql": ^0.38.1 + "@opentelemetry/instrumentation-grpc": ^0.49.1 + "@opentelemetry/instrumentation-hapi": ^0.35.0 + "@opentelemetry/instrumentation-http": ^0.49.1 + "@opentelemetry/instrumentation-ioredis": ^0.38.0 + "@opentelemetry/instrumentation-knex": ^0.34.0 + "@opentelemetry/instrumentation-koa": ^0.38.0 + "@opentelemetry/instrumentation-lru-memoizer": ^0.35.0 + "@opentelemetry/instrumentation-memcached": ^0.34.0 + "@opentelemetry/instrumentation-mongodb": ^0.41.0 + "@opentelemetry/instrumentation-mongoose": ^0.36.0 + "@opentelemetry/instrumentation-mysql": ^0.36.0 + "@opentelemetry/instrumentation-mysql2": ^0.36.0 + "@opentelemetry/instrumentation-nestjs-core": ^0.35.0 + "@opentelemetry/instrumentation-net": ^0.34.0 + "@opentelemetry/instrumentation-pg": ^0.39.1 + "@opentelemetry/instrumentation-pino": ^0.36.0 + "@opentelemetry/instrumentation-redis": ^0.37.0 + "@opentelemetry/instrumentation-redis-4": ^0.37.0 + "@opentelemetry/instrumentation-restify": ^0.36.0 + "@opentelemetry/instrumentation-router": ^0.35.0 + "@opentelemetry/instrumentation-socket.io": ^0.37.0 + "@opentelemetry/instrumentation-tedious": ^0.8.0 + "@opentelemetry/instrumentation-winston": ^0.35.0 + "@opentelemetry/resource-detector-alibaba-cloud": ^0.28.7 + "@opentelemetry/resource-detector-aws": ^1.4.0 + "@opentelemetry/resource-detector-container": ^0.3.7 + "@opentelemetry/resource-detector-gcp": ^0.29.7 + "@opentelemetry/resources": ^1.12.0 + "@opentelemetry/sdk-node": ^0.49.1 + peerDependencies: + "@opentelemetry/api": ^1.4.1 + checksum: e1bb1119a58f70186cffc8de192f1b3884ba10aa31976b513a7df0995a737383fd043c570142f6feb660db087e160c24356aa55d8fe22010394efca3ca9ceb0b + languageName: node + linkType: hard + +"@opentelemetry/context-async-hooks@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/context-async-hooks@npm:1.22.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 03b3b8c3eb34b35495abd9869303e67a61fafb8a004a9bc6ab1234a35909ee89d0f515cfeb5b710c9f3e8f4d185b776ada3fa2975a62d607c80986a7c46f4d83 + languageName: node + linkType: hard + +"@opentelemetry/context-async-hooks@npm:1.23.0": + version: 1.23.0 + resolution: "@opentelemetry/context-async-hooks@npm:1.23.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 4dc6c4f816402fe3deb5d43aebd4ceadd8afa8feab2047eed7cc906379fd341686cac8d16bce1c436d15e03b29883bcf73f04d4da005abe318e0b9ec69bdbd23 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/core@npm:1.22.0" + dependencies: + "@opentelemetry/semantic-conventions": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 0056bbaceb922816ec87e7e21aa8a7687377a41ba36a598bb6c49738d1eb5767f823e5758b5bf844d2b10aa075c553e98904dd6fd4f02c24cf335e3951fe78a6 + languageName: node + linkType: hard + "@opentelemetry/core@npm:1.23.0": version: 1.23.0 resolution: "@opentelemetry/core@npm:1.23.0" @@ -11926,6 +12135,17 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/core@npm:1.24.0, @opentelemetry/core@npm:^1.0.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.8.0": + version: 1.24.0 + resolution: "@opentelemetry/core@npm:1.24.0" + dependencies: + "@opentelemetry/semantic-conventions": 1.24.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: b1af2641cd3af62fae772c97701434e45fbb2bbd53403aa640a589548f852759279598134b4338ed48bcde6099e273b2f34686cbf1e817d566282e3b846397b7 + languageName: node + linkType: hard + "@opentelemetry/exporter-prometheus@npm:^0.50.0": version: 0.50.0 resolution: "@opentelemetry/exporter-prometheus@npm:0.50.0" @@ -11939,6 +12159,875 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.49.1" + dependencies: + "@grpc/grpc-js": ^1.7.1 + "@opentelemetry/core": 1.22.0 + "@opentelemetry/otlp-grpc-exporter-base": 0.49.1 + "@opentelemetry/otlp-transformer": 0.49.1 + "@opentelemetry/resources": 1.22.0 + "@opentelemetry/sdk-trace-base": 1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 2418f68c8027d5baf5609f7e32b524826ecdc16b3382f6113a6f1f00835e59275a51f2fdefcd5b6f73f455aab444892097866ce8e894d0415d70acd25a6a3049 + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-grpc@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/exporter-trace-otlp-grpc@npm:0.50.0" + dependencies: + "@grpc/grpc-js": ^1.7.1 + "@opentelemetry/core": 1.23.0 + "@opentelemetry/otlp-grpc-exporter-base": 0.50.0 + "@opentelemetry/otlp-transformer": 0.50.0 + "@opentelemetry/resources": 1.23.0 + "@opentelemetry/sdk-trace-base": 1.23.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: f27189ebf0ae4f417d7e3697a5679805da3544cc60082b89d2d66344a3a4e1941f2f9ab130954ea2718ed32f14201d5679b675d1eb026b7ed5f7b9f817243e57 + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-http@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.49.1" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/otlp-exporter-base": 0.49.1 + "@opentelemetry/otlp-transformer": 0.49.1 + "@opentelemetry/resources": 1.22.0 + "@opentelemetry/sdk-trace-base": 1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 35084da407169f7871c016f92df787446f7e133b8cf6a062b9e76e4779373dae7f054b98ace7709bd52b236f13a073b58fa599bf8fc09d5a9df09945bac0fa49 + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-http@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/exporter-trace-otlp-http@npm:0.50.0" + dependencies: + "@opentelemetry/core": 1.23.0 + "@opentelemetry/otlp-exporter-base": 0.50.0 + "@opentelemetry/otlp-transformer": 0.50.0 + "@opentelemetry/resources": 1.23.0 + "@opentelemetry/sdk-trace-base": 1.23.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: e0725be8f19f2c37c9b16989ff234183213878dad44f0698fc2b6a815c5979498c3766b0377932197ef051a0914d337c81a486612eac2285a4c9cb8313eefa6b + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-proto@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.49.1" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/otlp-exporter-base": 0.49.1 + "@opentelemetry/otlp-proto-exporter-base": 0.49.1 + "@opentelemetry/otlp-transformer": 0.49.1 + "@opentelemetry/resources": 1.22.0 + "@opentelemetry/sdk-trace-base": 1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 0afa21d176f087d52b68b84f5645076241e5b842461add7de4429f8860861d5141d5f84be144b74d7656d3928bc580f8ee12bb231b4d80861f73128f3b09b4fc + languageName: node + linkType: hard + +"@opentelemetry/exporter-trace-otlp-proto@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/exporter-trace-otlp-proto@npm:0.50.0" + dependencies: + "@opentelemetry/core": 1.23.0 + "@opentelemetry/otlp-exporter-base": 0.50.0 + "@opentelemetry/otlp-proto-exporter-base": 0.50.0 + "@opentelemetry/otlp-transformer": 0.50.0 + "@opentelemetry/resources": 1.23.0 + "@opentelemetry/sdk-trace-base": 1.23.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 9666686d85a0966373e5b01e55f6ae9bbe397a27efce4f2bd1eb861e993f5894724b5b5131c4a74061f6bb60b18ba946f27e38850fcd33204ca99f631317139e + languageName: node + linkType: hard + +"@opentelemetry/exporter-zipkin@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/exporter-zipkin@npm:1.22.0" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/resources": 1.22.0 + "@opentelemetry/sdk-trace-base": 1.22.0 + "@opentelemetry/semantic-conventions": 1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 8d3d396d3cf69d5b507abb980a1d548c6a93243d5413c371a8ed7a025990e02c04498f7232c2562279d74a1c4ec862292d6a3498a4513eebe9882d615226c5fd + languageName: node + linkType: hard + +"@opentelemetry/exporter-zipkin@npm:1.23.0": + version: 1.23.0 + resolution: "@opentelemetry/exporter-zipkin@npm:1.23.0" + dependencies: + "@opentelemetry/core": 1.23.0 + "@opentelemetry/resources": 1.23.0 + "@opentelemetry/sdk-trace-base": 1.23.0 + "@opentelemetry/semantic-conventions": 1.23.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 08d5f7a9e2af1ad749be8cb6e65d1b312d1e86dd9ec484156ddcd2c0ad3ff27dded459c599ec406d9bccb937e1fd5b58e9af6ff2e5efc3d2e4e83ca2af12920e + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-amqplib@npm:^0.35.0": + version: 0.35.0 + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.35.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 7f99738f85d56ee0b706330558936c92e2f2b6f91ae86896b3ac0891f40a42c5b8cee8b457a8efc12d4875ed73b29f36d9aa3590b779a1214d13a655b83268ce + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-lambda@npm:^0.39.0": + version: 0.39.0 + resolution: "@opentelemetry/instrumentation-aws-lambda@npm:0.39.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/propagator-aws-xray": ^1.3.1 + "@opentelemetry/resources": ^1.8.0 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/aws-lambda": 8.10.122 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: e2840a79680e70157f82341f7796e3d53e79c65c63bfc77995760a647e797fee4afb5dcf6220a672cf8dd3d2caa74ac15f1850a295b483d50d19dd0aeb7512d1 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-aws-sdk@npm:^0.39.1": + version: 0.39.1 + resolution: "@opentelemetry/instrumentation-aws-sdk@npm:0.39.1" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/propagation-utils": ^0.30.7 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 7d18489b2f161b9bbc2e2d25c23f7358dc5b4e623b76edfdac9f999c083b3b5dd49326b79f6dc1b19c74b9024683e12e1addf03d0817946b2f11481808bbf530 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-bunyan@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/instrumentation-bunyan@npm:0.36.0" + dependencies: + "@opentelemetry/api-logs": ^0.49.1 + "@opentelemetry/instrumentation": ^0.49.1 + "@types/bunyan": 1.8.9 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 4848164223c152381d435127a39d296da6445591a99c7d872c220e9605e1ff769537d3840f8d3b17bb4db8d8ccfec54c505e75a82f19117addef793ba990d74d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-cassandra-driver@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/instrumentation-cassandra-driver@npm:0.36.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: f978269c922b1a1880ad792accfe359073c0e2ba6060ea75060c9785115aab0b6ce39cfe861a4f358e3da90ad2a7d9f53d52cbf54ac94fd65602bfd732709a32 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-connect@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.34.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/connect": 3.4.36 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: dde9880cd00490bfc52dca8b3b1be6a4951e859fa98617e6db78ec95ce84e8d3df3bbbbe5711796a13bd672aa2d89ba4439621a70614f8b1400943cc439540ad + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-cucumber@npm:^0.4.0": + version: 0.4.0 + resolution: "@opentelemetry/instrumentation-cucumber@npm:0.4.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 4126c5eaf5d48daca410b0ce76bd4d97f6e868fe0b0c151f88f379a93a1d95a1b7e934cb876d0060182f1b9dd20012aac13fc9c7baa6dd4f0c7f903fdfdce54e + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-dataloader@npm:^0.7.0": + version: 0.7.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.7.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: bbd5f27ff770ccb7d79bf16c81c5de9ad84e0c27df8991f8fd36560f190bbb7b7b7d9c8c62d438577f18033447795519a16f66e650f8e009031ffc52a96be825 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-dns@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-dns@npm:0.34.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + semver: ^7.5.4 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 9c2b4aae823255d88590c39802a76df888b745f744442930e322e9f48558090a9e08f91c7cd7433d48fac97dc7747ed636446277f3f163dcaecc0c70c53d0604 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-express@npm:^0.36.1": + version: 0.36.1 + resolution: "@opentelemetry/instrumentation-express@npm:0.36.1" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: c4f4ed644a194160dd816e20cd914ab59b7927c11b2c1639fb17bb160a59148a027b23c3259d3ec3ac3384b90c769b15bedd7aac4649ac3ae01ee2cdb746f61d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fastify@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.34.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: c37d8f889b1b1db87fa066d41d595220505b6b708183daae06a0b8db94140a5f6d294bcc5650f024385626e114e5d926b1679c8246008be40a540a2f5710b1c5 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-fs@npm:^0.10.0": + version: 0.10.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.10.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 4382ddeb28b663d218f47b2db533c2f4a350d9c38ca71b1a4b688b54f8f0333ce9f54ed411e294f0ce05cbf339897b2379ee8dfd7aaf6b3109077c86cc629a94 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-generic-pool@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.34.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 0d55b59e65b6cf2af9942d303bc550da66455b718be5778330a0538f5c6ca27c18a2f82cb88dbeab98d4d8fb37ce761de1cd448e338701e57fd61bdb3b60b12a + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-graphql@npm:^0.38.1": + version: 0.38.1 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.38.1" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: d148d1aa5ab661ab375bce5a0539e80836e0dc695936661a93e64a4f58b7b7c9617cca6d9989b15f6d7fa2a5ffbdd8c26ba43316416cdf527ce03cb9fc9165ac + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-grpc@npm:^0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/instrumentation-grpc@npm:0.49.1" + dependencies: + "@opentelemetry/instrumentation": 0.49.1 + "@opentelemetry/semantic-conventions": 1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 55b22e2dffb6be63d57c97773b63fed9576307676d7919b16b005f47bce13a103437160966f2c01c2ed5aa71f135b36248d3db69c3030685a1e8ea9d4c42d4f9 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-hapi@npm:^0.35.0": + version: 0.35.0 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.35.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/hapi__hapi": 20.0.13 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 23545b19597e3f7f65fb0ff06adb6d5ed9ce4675ebbc5153c243668b0476f99d298e097b7f2349b3eaa565bcab0c26e7985332b749624afd5b77172669b70217 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-http@npm:^0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/instrumentation-http@npm:0.49.1" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/instrumentation": 0.49.1 + "@opentelemetry/semantic-conventions": 1.22.0 + semver: ^7.5.2 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: b3dc388f94a69202749cca70d71b5bcd3b713d67c40e0421b84d11a323080a76d645ea05fc551092022451cbbef6da303d806890e90f1784edde65adefd20e4d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-ioredis@npm:^0.38.0": + version: 0.38.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.38.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/redis-common": ^0.36.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/ioredis4": "npm:@types/ioredis@^4.28.10" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 0b61f92db80ff89d00e93f3ef8b532c9439973ac0fd7d6b492a5faf46dc61d3c0b65b6b4359d69d23413db59b85e86a3857d82b00f810dd22be41014b3ff392d + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-knex@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-knex@npm:0.34.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 544b280c7a508bf4930f3d99dbce844358d066a1b7455c389916a6c13cab9d43ac5ff6eb1dd7e147988bc2636ca0e979f62883ee9c5941abcda29ba168af6165 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-koa@npm:^0.38.0": + version: 0.38.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.38.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/koa": 2.14.0 + "@types/koa__router": 12.0.3 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 7b76f6d03cbaeeda3a8a1e40790b76b52587365bd1ee2d2b04b65d62a527b9085c13827398e0422be4432f1b0dde3621246e7cbd85fa489aced58e085702e201 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-lru-memoizer@npm:^0.35.0": + version: 0.35.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.35.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 9ef0231cf21c747fac86134402f36ea8561e8df694bc1e536b0f54af536e0a675e38c8d34c22628afbe6bc65ab5b5edebfe4de7b45d42e6e9d616afb60db62bf + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-memcached@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-memcached@npm:0.34.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/memcached": ^2.2.6 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: b2a9d97f78641074054e9a67458702785fb15ceb7c1e4868ab8f5ddde85826676e29094154fac19b0722a58a62e1311afc5fda69e883b2bd7e4780bb8dd3f2cb + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongodb@npm:^0.41.0": + version: 0.41.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.41.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/sdk-metrics": ^1.9.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 8fb991a1ea05a559d369e0274825b1f7e2a27f2aa4ddb8ecbf4e329efa35e115a19bd75864597918b74773319ad9dcbaf846e621dd286543d4f732399ab942b1 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mongoose@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.36.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 7bc18b731f321090550e02930f2a2ffa4a99d7becd58b3cfba16c89f8c5e91a97e59abac4976864c98c13b5ce171f9b4131c723b13ec62657d9a0d8e3f74e79f + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql2@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.36.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@opentelemetry/sql-common": ^0.40.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 3395f0d69c23d3a98e9e4d7085a36868b938eb5337c5f5bd540d6b4d025d90a6af89f28e01635e2558d963d2c28be1ba6bf63364767575dd39383fd6dfc82c89 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-mysql@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.36.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/mysql": 2.15.22 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 705ab9e38e79a6db3be59eac718814c1cc71dea7d45e4da5257094625940f1e84002d9c5c8afa33f5bbc25ff012a66988d02b5be8fac521e2fc0fae353d9e013 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-nestjs-core@npm:^0.35.0": + version: 0.35.0 + resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.35.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: f878dc88d65e4ac876ca36a2e1f9c109aba14dd97f7aa8863b2231d90b57cb2526c74124690661a4d5fad8936d8c9b44c63c46563a5b89a1a57c86eb0ba01efd + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-net@npm:^0.34.0": + version: 0.34.0 + resolution: "@opentelemetry/instrumentation-net@npm:0.34.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: ed4dd2a9ea69314d519358733fcaee8a55d120cb8a5e85088b20626b753e5ddeea7a61e58b76d1e86b27c36828e9777a504ba405556046e9a30ef1bf12eeaa08 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pg@npm:^0.39.1": + version: 0.39.1 + resolution: "@opentelemetry/instrumentation-pg@npm:0.39.1" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@opentelemetry/sql-common": ^0.40.0 + "@types/pg": 8.6.1 + "@types/pg-pool": 2.0.4 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 5e6276aaed45e88ccf019ce533a905570340160752e08e7e85c1bdfbe646ca6779046afd46c759cba18b2acec3dcb5bf80591807cf335bc2583d577725fdede8 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-pino@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/instrumentation-pino@npm:0.36.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: bf46db42c88930f64430a68c227b5b08deeb40518ad082a610524ad8efc5768d038117951032e89a86131eb0f95106368cba8d3fde091f906aa3e42e167c2c95 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-redis-4@npm:^0.37.0": + version: 0.37.0 + resolution: "@opentelemetry/instrumentation-redis-4@npm:0.37.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/redis-common": ^0.36.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 98208f8fe83bcf5e4fc9efeadff8724bc9eba4cfddc9c76630a30a582583e4d49842a47730429705c1d5931acbcc7223a4538194c43373c30ea6b0e326721db8 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-redis@npm:^0.37.0": + version: 0.37.0 + resolution: "@opentelemetry/instrumentation-redis@npm:0.37.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/redis-common": ^0.36.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 06ceaf8df2cfc7f197f981b6785af3160379eb94c4a7b3e0a0e60ca78c47b3f22599d3e9f030c52ff016da396418a0c31eb38282b207d0477aa42e8c9180ac2c + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-restify@npm:^0.36.0": + version: 0.36.0 + resolution: "@opentelemetry/instrumentation-restify@npm:0.36.0" + dependencies: + "@opentelemetry/core": ^1.8.0 + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: f7dbca7e2a10463575d33f27c9c7e3b9352732dfe3df646fa0772a61aaae4757d1e66ede740ebe41804a952de904447cc6823c82ece31db16b568f926e40bcaa + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-router@npm:^0.35.0": + version: 0.35.0 + resolution: "@opentelemetry/instrumentation-router@npm:0.35.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 3bdfba103b41f10c7fe7a650ad4f54cfa7a53b38afe69e05a0402b0157f2c393543b45e42febf05cc5c964ecb8667374a60ad25d00ce021180c70c02770e5f63 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-socket.io@npm:^0.37.0": + version: 0.37.0 + resolution: "@opentelemetry/instrumentation-socket.io@npm:0.37.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 21b790f53b91994a1241d7d3c5a400d5232001f740c13f64a0f48461e5db7974d2563e17059a44f567fb8dc4fb9078dc57738784b1474fb40e771e5d7bca67bb + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-tedious@npm:^0.8.0": + version: 0.8.0 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.8.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + "@opentelemetry/semantic-conventions": ^1.0.0 + "@types/tedious": ^4.0.10 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 920a8446fb765f6833d926680ae4e6a7e7e4013e2f9145b20546e945848e3f9a31aabb6e37c087a9a7667457c7ec710cd5d6304279e67a6c7ddeda0d41501e01 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-winston@npm:^0.35.0": + version: 0.35.0 + resolution: "@opentelemetry/instrumentation-winston@npm:0.35.0" + dependencies: + "@opentelemetry/instrumentation": ^0.49.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: f4f2ef0adf049de6434906fd573e2465900e1f5c0f788c842978f6a43889c12dc04d15a8c079a615e9fe09df1eeabc0c2b93a219aff6f1106b2031b01f045d96 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:0.49.1, @opentelemetry/instrumentation@npm:^0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/instrumentation@npm:0.49.1" + dependencies: + "@opentelemetry/api-logs": 0.49.1 + "@types/shimmer": ^1.0.2 + import-in-the-middle: 1.7.1 + require-in-the-middle: ^7.1.1 + semver: ^7.5.2 + shimmer: ^1.2.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 87379f8505118c850f73947784fd57fbaa2667fbf5ca8bd0a91a0782d0a240ab92e9091cd4107a6785d37e4976a6f1fb20b89a1d9ac9bec6faf858681a7d8707 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/instrumentation@npm:0.50.0" + dependencies: + "@opentelemetry/api-logs": 0.50.0 + "@types/shimmer": ^1.0.2 + import-in-the-middle: 1.7.1 + require-in-the-middle: ^7.1.1 + semver: ^7.5.2 + shimmer: ^1.2.1 + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 371398639ca68c188d4b77a0034ea369222a2a1de421be37190900bade1210802a50b53cc48fd21206917817319a92a4cb52bd92bd534889355b54316145e634 + languageName: node + linkType: hard + +"@opentelemetry/otlp-exporter-base@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.49.1" + dependencies: + "@opentelemetry/core": 1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 43b2237b83811ccb632b0e6a5a6b824627c1c05670c0ca158f0686278ec6d1dc32d8780d0fde8c496531e6b7fbe780e154aa5dc53b911b49c927f0566bcc795d + languageName: node + linkType: hard + +"@opentelemetry/otlp-exporter-base@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.50.0" + dependencies: + "@opentelemetry/core": 1.23.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: e1e6586a64d753e542f28858c1618776deeaf639afd50e4ff325c14f7571ac91546928d0f2f89b2287e7cdbadf3211e3f9dd844139f64e6253e99f71b6cc1f07 + languageName: node + linkType: hard + +"@opentelemetry/otlp-grpc-exporter-base@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.49.1" + dependencies: + "@grpc/grpc-js": ^1.7.1 + "@opentelemetry/core": 1.22.0 + "@opentelemetry/otlp-exporter-base": 0.49.1 + protobufjs: ^7.2.3 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 7d8065ea161ba105060856ec448d2fdbbb725fd397bfd47a3281929249af3995373619d8e8c6504ca22893d1e1cffe12f3af220988d06e2b1576c8ac26edb86e + languageName: node + linkType: hard + +"@opentelemetry/otlp-grpc-exporter-base@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/otlp-grpc-exporter-base@npm:0.50.0" + dependencies: + "@grpc/grpc-js": ^1.7.1 + "@opentelemetry/core": 1.23.0 + "@opentelemetry/otlp-exporter-base": 0.50.0 + protobufjs: ^7.2.3 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 97a4e69d2834c840f1f037737eb378c309e5645002490b5bc51b836303a205a65658e2b1ec512dd613207626517efbe744b46225a9b3079b782cdda0a168fcf6 + languageName: node + linkType: hard + +"@opentelemetry/otlp-proto-exporter-base@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/otlp-proto-exporter-base@npm:0.49.1" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/otlp-exporter-base": 0.49.1 + protobufjs: ^7.2.3 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: b381e1022855867b621ad04252cad7376445c6a110073c8715f1c41b782b5dbad668f2acee2aecfca348c49a3f2adb4c16297d99fe9d3068b1746314cc6d4c13 + languageName: node + linkType: hard + +"@opentelemetry/otlp-proto-exporter-base@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/otlp-proto-exporter-base@npm:0.50.0" + dependencies: + "@opentelemetry/core": 1.23.0 + "@opentelemetry/otlp-exporter-base": 0.50.0 + protobufjs: ^7.2.3 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: d39f61a5ca31ac9cbb6ac7e77c71afebc7b40bf247361c99e34f8c00aeae88db1d4c7e6742f9dd01d2334ee50c7db8e24d7d9df67a65ee005c947358611f2050 + languageName: node + linkType: hard + +"@opentelemetry/otlp-transformer@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/otlp-transformer@npm:0.49.1" + dependencies: + "@opentelemetry/api-logs": 0.49.1 + "@opentelemetry/core": 1.22.0 + "@opentelemetry/resources": 1.22.0 + "@opentelemetry/sdk-logs": 0.49.1 + "@opentelemetry/sdk-metrics": 1.22.0 + "@opentelemetry/sdk-trace-base": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.9.0" + checksum: 1f79e796a452168f353a3dfead3ae3e3206a30fa68d690721d7377e78f9e2ba0f3de32621fd8743b3c47c9b8c22034bf7f9b209327d2d09caa8a08f13288eeb0 + languageName: node + linkType: hard + +"@opentelemetry/otlp-transformer@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/otlp-transformer@npm:0.50.0" + dependencies: + "@opentelemetry/api-logs": 0.50.0 + "@opentelemetry/core": 1.23.0 + "@opentelemetry/resources": 1.23.0 + "@opentelemetry/sdk-logs": 0.50.0 + "@opentelemetry/sdk-metrics": 1.23.0 + "@opentelemetry/sdk-trace-base": 1.23.0 + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.9.0" + checksum: d2637146cdb1a3c7c311f03c8d8a11c1c4b57c63ac3532865096055d603a334bb4b5a63cecaba96f27dd0f3b8b4c7ffcebd248d85c47b7e60a5b5e7ae821219c + languageName: node + linkType: hard + +"@opentelemetry/propagation-utils@npm:^0.30.7": + version: 0.30.9 + resolution: "@opentelemetry/propagation-utils@npm:0.30.9" + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 4cc6e645ed7334fc1773396854c95aaabd5c5a307ede85ee8533e362fff4fa290a6efa36b35ced7a7d73894f575d07bd4daae4331ddd62536a4847efdd9354b0 + languageName: node + linkType: hard + +"@opentelemetry/propagator-aws-xray@npm:^1.3.1": + version: 1.24.0 + resolution: "@opentelemetry/propagator-aws-xray@npm:1.24.0" + dependencies: + "@opentelemetry/core": 1.24.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 51d78403749e110c31f916a82edef2c5d1df1e8a1a9ada8dd420bf3ef5c1baafb0748193cd74fbb3975745110afe99602d5977a0e38690c9ba53e2166c28d4c5 + languageName: node + linkType: hard + +"@opentelemetry/propagator-b3@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/propagator-b3@npm:1.22.0" + dependencies: + "@opentelemetry/core": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: f072fcfaa2c126b84f7f9b060fa66fff36fbe3f425123f6aec9a99094d20296dc781d3c61e765b2727e957fcad311fd4f58da70ed51bf152fbc21202d78d70a5 + languageName: node + linkType: hard + +"@opentelemetry/propagator-b3@npm:1.23.0": + version: 1.23.0 + resolution: "@opentelemetry/propagator-b3@npm:1.23.0" + dependencies: + "@opentelemetry/core": 1.23.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 8478a4ac3fcad3ea53ed7af70c7da22dee48a262aef2bb2c64a039a3aff1368476b23ce385b95b3c34334a29d7964c98ca3c08bc05dd2999a891faf3ab858799 + languageName: node + linkType: hard + +"@opentelemetry/propagator-jaeger@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/propagator-jaeger@npm:1.22.0" + dependencies: + "@opentelemetry/core": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 0d998bd160b6d812ebc61b8ba75009688cf2d0feaeb4389c8f6bb32d2a02c16f7cf6a77d1bc5f773bf5663f31ca3fa7ec2dc79cf6d9909ffb5098297f6357a86 + languageName: node + linkType: hard + +"@opentelemetry/propagator-jaeger@npm:1.23.0": + version: 1.23.0 + resolution: "@opentelemetry/propagator-jaeger@npm:1.23.0" + dependencies: + "@opentelemetry/core": 1.23.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 240f27f15473704a5cf8549ea22b1640d865ef40c3da65b023e7f44cc89422b9f983061e4508a3f4576339907625c8f725c42e7f5e77c243c833e03d9490880d + languageName: node + linkType: hard + +"@opentelemetry/redis-common@npm:^0.36.1": + version: 0.36.2 + resolution: "@opentelemetry/redis-common@npm:0.36.2" + checksum: b0a6f2c2dc64ba3b655ed944a5a33715d00365865e6f498005527a4ad6c40ca0e7b8ac531791b6d5abfbab9b22d9c6aa1cd8bcc851a7634dfb381ad2d5061b0d + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-alibaba-cloud@npm:^0.28.7": + version: 0.28.9 + resolution: "@opentelemetry/resource-detector-alibaba-cloud@npm:0.28.9" + dependencies: + "@opentelemetry/resources": ^1.0.0 + "@opentelemetry/semantic-conventions": ^1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 8ac05377da60c7c1a958509849989d2151a29c341819def6033f65e5302066dec77ad0358a117d40eef22cbb3537956bb492a15e524a86131f92e0e5d9a91c16 + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-aws@npm:^1.4.0": + version: 1.4.2 + resolution: "@opentelemetry/resource-detector-aws@npm:1.4.2" + dependencies: + "@opentelemetry/core": ^1.0.0 + "@opentelemetry/resources": ^1.0.0 + "@opentelemetry/semantic-conventions": ^1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 66a7c90f037bccb0a754701182b6ec89982f8ba1d46bc07c0b4db6debe69dc504482fe65bdb3867c8ddf8f18e899a657cbb0c6bf5e677becb2ec08be8c3538f1 + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-container@npm:^0.3.7": + version: 0.3.9 + resolution: "@opentelemetry/resource-detector-container@npm:0.3.9" + dependencies: + "@opentelemetry/resources": ^1.0.0 + "@opentelemetry/semantic-conventions": ^1.22.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: c2157f4d9b602c6fd32ad27c2b0ee8dd944542b18c025363d47fd9b38458ee2107f442dab957e8d4107c8ac7cc049cdc09779965cb309ab3fedb5b52fafbcb30 + languageName: node + linkType: hard + +"@opentelemetry/resource-detector-gcp@npm:^0.29.7": + version: 0.29.9 + resolution: "@opentelemetry/resource-detector-gcp@npm:0.29.9" + dependencies: + "@opentelemetry/core": ^1.0.0 + "@opentelemetry/resources": ^1.0.0 + "@opentelemetry/semantic-conventions": ^1.22.0 + gcp-metadata: ^6.0.0 + peerDependencies: + "@opentelemetry/api": ^1.0.0 + checksum: 4829cef6e5d849cae34320751c172ac9583ae12a9336db48ffa8ae47c288ca8d86d5b2ec19f20881fdd9505b6f3f12eeadd59f9dd341187dc8702f0a68d7e4d0 + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/resources@npm:1.22.0" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/semantic-conventions": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: f1da492d9fa7dbe3e5f08a511654b45265fcd16c4695bb1ae92488baa45ae9910d5a962166aaa4ae63be4c75393680c6f064450a5f10b86501b9df427ac49f27 + languageName: node + linkType: hard + "@opentelemetry/resources@npm:1.23.0": version: 1.23.0 resolution: "@opentelemetry/resources@npm:1.23.0" @@ -11951,7 +13040,58 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/sdk-metrics@npm:1.23.0, @opentelemetry/sdk-metrics@npm:^1.13.0": +"@opentelemetry/resources@npm:1.24.0, @opentelemetry/resources@npm:^1.0.0, @opentelemetry/resources@npm:^1.12.0, @opentelemetry/resources@npm:^1.8.0": + version: 1.24.0 + resolution: "@opentelemetry/resources@npm:1.24.0" + dependencies: + "@opentelemetry/core": 1.24.0 + "@opentelemetry/semantic-conventions": 1.24.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: b9a59d4267388aaec8d4adc1d708220209bdba1f60ef80fdf1436a23a4e1e04d0c05c33bf1cd08bec7ab75d1b7d2311d25bbe62253bd1d6efbb64102a7018958 + languageName: node + linkType: hard + +"@opentelemetry/sdk-logs@npm:0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/sdk-logs@npm:0.49.1" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/resources": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.4.0 <1.9.0" + "@opentelemetry/api-logs": ">=0.39.1" + checksum: 9c2a60a15fd5a40316b96805a23d96dffb62258d78d20edcc0a9dedcb89aa410ab592d67c315fa63138657cb717d7e9e406337b16bee89a7115bdfe8a190190b + languageName: node + linkType: hard + +"@opentelemetry/sdk-logs@npm:0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/sdk-logs@npm:0.50.0" + dependencies: + "@opentelemetry/core": 1.23.0 + "@opentelemetry/resources": 1.23.0 + peerDependencies: + "@opentelemetry/api": ">=1.4.0 <1.9.0" + "@opentelemetry/api-logs": ">=0.39.1" + checksum: e93be98f4ea2b64dd0fc0aebc5dfa7276f995a0822cae727455988397cb0c10f7696dbabc4d01a7b09d515faceffa2858c3329a85841ae79c44072e8c0911df8 + languageName: node + linkType: hard + +"@opentelemetry/sdk-metrics@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/sdk-metrics@npm:1.22.0" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/resources": 1.22.0 + lodash.merge: ^4.6.2 + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.9.0" + checksum: 43b6599432bece2e41a48d40653f4f928ad7ba0b74c50a17bdb38f13bcb47cec1e08ce9af7f8cc643dc2c28cb127ef5ef4ce3e53cd1bf386d54fdaca361f29f2 + languageName: node + linkType: hard + +"@opentelemetry/sdk-metrics@npm:1.23.0": version: 1.23.0 resolution: "@opentelemetry/sdk-metrics@npm:1.23.0" dependencies: @@ -11964,6 +13104,130 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/sdk-metrics@npm:^1.9.1": + version: 1.24.0 + resolution: "@opentelemetry/sdk-metrics@npm:1.24.0" + dependencies: + "@opentelemetry/core": 1.24.0 + "@opentelemetry/resources": 1.24.0 + lodash.merge: ^4.6.2 + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.9.0" + checksum: 4468302b048685fa06c03c434754a37a671c4b1ae9a0409ad53132742eac7c982a65712bee4614f2d46e1fd361ec012afc55f693f00316808573f5c427cb68b9 + languageName: node + linkType: hard + +"@opentelemetry/sdk-node@npm:^0.49.1": + version: 0.49.1 + resolution: "@opentelemetry/sdk-node@npm:0.49.1" + dependencies: + "@opentelemetry/api-logs": 0.49.1 + "@opentelemetry/core": 1.22.0 + "@opentelemetry/exporter-trace-otlp-grpc": 0.49.1 + "@opentelemetry/exporter-trace-otlp-http": 0.49.1 + "@opentelemetry/exporter-trace-otlp-proto": 0.49.1 + "@opentelemetry/exporter-zipkin": 1.22.0 + "@opentelemetry/instrumentation": 0.49.1 + "@opentelemetry/resources": 1.22.0 + "@opentelemetry/sdk-logs": 0.49.1 + "@opentelemetry/sdk-metrics": 1.22.0 + "@opentelemetry/sdk-trace-base": 1.22.0 + "@opentelemetry/sdk-trace-node": 1.22.0 + "@opentelemetry/semantic-conventions": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.9.0" + checksum: eb1ef8ddb33de7a4e8be697ed0eca100a4d728f41f7c8fca5a06f3facf5a1c2a4fce3d9585a29f47d958904a920024dd6637907e810cc9fb4b702cc8970136eb + languageName: node + linkType: hard + +"@opentelemetry/sdk-node@npm:^0.50.0": + version: 0.50.0 + resolution: "@opentelemetry/sdk-node@npm:0.50.0" + dependencies: + "@opentelemetry/api-logs": 0.50.0 + "@opentelemetry/core": 1.23.0 + "@opentelemetry/exporter-trace-otlp-grpc": 0.50.0 + "@opentelemetry/exporter-trace-otlp-http": 0.50.0 + "@opentelemetry/exporter-trace-otlp-proto": 0.50.0 + "@opentelemetry/exporter-zipkin": 1.23.0 + "@opentelemetry/instrumentation": 0.50.0 + "@opentelemetry/resources": 1.23.0 + "@opentelemetry/sdk-logs": 0.50.0 + "@opentelemetry/sdk-metrics": 1.23.0 + "@opentelemetry/sdk-trace-base": 1.23.0 + "@opentelemetry/sdk-trace-node": 1.23.0 + "@opentelemetry/semantic-conventions": 1.23.0 + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.9.0" + checksum: 1ca47e0cec7832a291e20fc838bfb2a7307b6d041a29b0ace1a5f3eb9f77fa8bad6e40d55eb19e5ab7c153afb866890bfc415e843d6b950691527566a592ad35 + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/sdk-trace-base@npm:1.22.0" + dependencies: + "@opentelemetry/core": 1.22.0 + "@opentelemetry/resources": 1.22.0 + "@opentelemetry/semantic-conventions": 1.22.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 9a15bca01532b9bd279fcf7c083af7e39c24187c6793aa6ce0e6f780394abc12458fc5ae67bff279afd9392ccd67f5d1823c326d4f4511d789667ba2a878d56c + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:1.23.0": + version: 1.23.0 + resolution: "@opentelemetry/sdk-trace-base@npm:1.23.0" + dependencies: + "@opentelemetry/core": 1.23.0 + "@opentelemetry/resources": 1.23.0 + "@opentelemetry/semantic-conventions": 1.23.0 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 564a14a38b151d793949da95949a5eb4e0034ff95356162a7fcf7fe6a81b312cd8d601d6e46b303e6d9f785152ff28621cb7bd114f61e064bfdfa77ed28ca8cc + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-node@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/sdk-trace-node@npm:1.22.0" + dependencies: + "@opentelemetry/context-async-hooks": 1.22.0 + "@opentelemetry/core": 1.22.0 + "@opentelemetry/propagator-b3": 1.22.0 + "@opentelemetry/propagator-jaeger": 1.22.0 + "@opentelemetry/sdk-trace-base": 1.22.0 + semver: ^7.5.2 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: fcd755be1355b211551e54d6a36d46f196fd9bec30b2eff0dc935082a59a86df3f9e9460a6d0b1f540063627f0baf1780f504ec13653a08a2954985d06888ac0 + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-node@npm:1.23.0": + version: 1.23.0 + resolution: "@opentelemetry/sdk-trace-node@npm:1.23.0" + dependencies: + "@opentelemetry/context-async-hooks": 1.23.0 + "@opentelemetry/core": 1.23.0 + "@opentelemetry/propagator-b3": 1.23.0 + "@opentelemetry/propagator-jaeger": 1.23.0 + "@opentelemetry/sdk-trace-base": 1.23.0 + semver: ^7.5.2 + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.9.0" + checksum: 165f26d77672d6745e9b1d3af78e3b1afcd4fe1b48e0eaef1aa67e9c86e822f9d0947cb0066c6a1080bdcf03c9da268870cace839254d1fee03446b25dbf6d30 + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:1.22.0": + version: 1.22.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.22.0" + checksum: cb3bdca1a29d3c32c44599bdf5ee5143b84e81aaa61edcd3f750133bfaffd7c1b36755c877921e4993e2468284a0564388844a7dda388122bee486d3f67fa4c8 + languageName: node + linkType: hard + "@opentelemetry/semantic-conventions@npm:1.23.0": version: 1.23.0 resolution: "@opentelemetry/semantic-conventions@npm:1.23.0" @@ -11971,6 +13235,24 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/semantic-conventions@npm:1.24.0, @opentelemetry/semantic-conventions@npm:^1.0.0, @opentelemetry/semantic-conventions@npm:^1.22.0": + version: 1.24.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.24.0" + checksum: ba7c71602f3eddc3f015457cf1183bd24f0300b2636b57cafe2e5196ae233daf05e573e3a7b954818e8f2d9543a44282a0406f327b9c066ae948eea5f4a91d27 + languageName: node + linkType: hard + +"@opentelemetry/sql-common@npm:^0.40.0": + version: 0.40.1 + resolution: "@opentelemetry/sql-common@npm:0.40.1" + dependencies: + "@opentelemetry/core": ^1.1.0 + peerDependencies: + "@opentelemetry/api": ^1.1.0 + checksum: 23529740531937dee137c9680dbd2f7abf6a7d7340fbd48d309707601fa6255a5e8c2626c8e1c285b49c0b3429f2b3a8e6cbf7f7240820ecfeb52e2ba5ed6740 + languageName: node + linkType: hard + "@oriflame/backstage-plugin-score-card@npm:^0.8.0": version: 0.8.0 resolution: "@oriflame/backstage-plugin-score-card@npm:0.8.0" @@ -13303,6 +14585,29 @@ __metadata: languageName: node linkType: hard +"@sideway/address@npm:^4.1.5": + version: 4.1.5 + resolution: "@sideway/address@npm:4.1.5" + dependencies: + "@hapi/hoek": ^9.0.0 + checksum: 3e3ea0f00b4765d86509282290368a4a5fd39a7995fdc6de42116ca19a96120858e56c2c995081def06e1c53e1f8bccc7d013f6326602bec9d56b72ee2772b9d + languageName: node + linkType: hard + +"@sideway/formula@npm:^3.0.1": + version: 3.0.1 + resolution: "@sideway/formula@npm:3.0.1" + checksum: e4beeebc9dbe2ff4ef0def15cec0165e00d1612e3d7cea0bc9ce5175c3263fc2c818b679bd558957f49400ee7be9d4e5ac90487e1625b4932e15c4aa7919c57a + languageName: node + linkType: hard + +"@sideway/pinpoint@npm:^2.0.0": + version: 2.0.0 + resolution: "@sideway/pinpoint@npm:2.0.0" + checksum: 0f4491e5897fcf5bf02c46f5c359c56a314e90ba243f42f0c100437935daa2488f20482f0f77186bd6bf43345095a95d8143ecf8b1f4d876a7bc0806aba9c3d2 + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -15343,6 +16648,15 @@ __metadata: languageName: node linkType: hard +"@types/accepts@npm:*": + version: 1.3.7 + resolution: "@types/accepts@npm:1.3.7" + dependencies: + "@types/node": "*" + checksum: 7678cf74976e16093aff6e6f9755826faf069ac1e30179276158ce46ea246348ff22ca6bdd46cef08428881337d9ceefbf00bab08a7731646eb9fc9449d6a1e7 + languageName: node + linkType: hard + "@types/ansi-regex@npm:^5.0.0": version: 5.0.0 resolution: "@types/ansi-regex@npm:5.0.0" @@ -15375,10 +16689,10 @@ __metadata: languageName: node linkType: hard -"@types/aws-lambda@npm:^8.10.83": - version: 8.10.92 - resolution: "@types/aws-lambda@npm:8.10.92" - checksum: 71c44d83a1c88aa6dbc920baedfb2d100b8843a3d210c695ccaafb30dfb75f04398b0e5368100022acbf75c55d456c61774242f20dd70915fc63d85430cbcf8a +"@types/aws-lambda@npm:8.10.122, @types/aws-lambda@npm:^8.10.83": + version: 8.10.122 + resolution: "@types/aws-lambda@npm:8.10.122" + checksum: 5c2e02ae8fc0eea90fa3b1014f401a8567695e65910fb53452e813b9b58761c956fba50ac7da606b97e07d881d264ff513573d279e7116f3c6b9590fdb093f31 languageName: node linkType: hard @@ -15467,6 +16781,15 @@ __metadata: languageName: node linkType: hard +"@types/bunyan@npm:1.8.9": + version: 1.8.9 + resolution: "@types/bunyan@npm:1.8.9" + dependencies: + "@types/node": "*" + checksum: 0635ca1906acda4fbce5aed0b9ba16c857e13081724ae5d30aae61083f03f80b299f05e8e573e2804e530ec4b7c2a68ee7f2f522afde664a41122d16e0a39db0 + languageName: node + linkType: hard + "@types/cacheable-request@npm:^6.0.1": version: 6.0.1 resolution: "@types/cacheable-request@npm:6.0.1" @@ -15557,12 +16880,19 @@ __metadata: languageName: node linkType: hard -"@types/connect@npm:*": - version: 3.4.33 - resolution: "@types/connect@npm:3.4.33" +"@types/connect@npm:*, @types/connect@npm:3.4.36": + version: 3.4.36 + resolution: "@types/connect@npm:3.4.36" dependencies: "@types/node": "*" - checksum: 1220403e0cd05c6f51c03b83eed0f4e086f252d50c13279effd38d8bfea5cae82db012b134d31004cb8e4705f83d8ad62dddd71028baa190bf6f31c8d9ac916b + checksum: 4dee3d966fb527b98f0cbbdcf6977c9193fc3204ed539b7522fe5e64dfa45f9017bdda4ffb1f760062262fce7701a0ee1c2f6ce2e50af36c74d4e37052303172 + languageName: node + linkType: hard + +"@types/content-disposition@npm:*": + version: 0.5.8 + resolution: "@types/content-disposition@npm:0.5.8" + checksum: eeea868fb510ae7a32aa2d7de680fba79d59001f3e758a334621e10bc0a6496d3a42bb79243a5e53b9c63cb524522853ccc144fe1ab160c4247d37cdb81146c4 languageName: node linkType: hard @@ -15596,6 +16926,18 @@ __metadata: languageName: node linkType: hard +"@types/cookies@npm:*": + version: 0.9.0 + resolution: "@types/cookies@npm:0.9.0" + dependencies: + "@types/connect": "*" + "@types/express": "*" + "@types/keygrip": "*" + "@types/node": "*" + checksum: ce59bfdf3a5d750400ac32aa93157ec7be997dc632660cf0bbfd76df23d71a70bb5f0820558cd26b9a5576f86b6664a2fd23ae211b51202a5b2f9a15995d7331 + languageName: node + linkType: hard + "@types/core-js@npm:^2.5.4": version: 2.5.8 resolution: "@types/core-js@npm:2.5.8" @@ -15872,6 +17214,47 @@ __metadata: languageName: node linkType: hard +"@types/hapi__catbox@npm:*": + version: 10.2.6 + resolution: "@types/hapi__catbox@npm:10.2.6" + checksum: 06cd8f4bced5ee912ec89daa53c10a416ab8d7c25ebf981dc3525a9fcc744afcfafd7353146ab8612a71b8905851e44613579ef89ab0c0cef2def0f27aeac480 + languageName: node + linkType: hard + +"@types/hapi__hapi@npm:20.0.13": + version: 20.0.13 + resolution: "@types/hapi__hapi@npm:20.0.13" + dependencies: + "@hapi/boom": ^9.0.0 + "@hapi/iron": ^6.0.0 + "@hapi/podium": ^4.1.3 + "@types/hapi__catbox": "*" + "@types/hapi__mimos": "*" + "@types/hapi__shot": "*" + "@types/node": "*" + joi: ^17.3.0 + checksum: 02d0f91b3b0900e05b6e8d31ae664e20d9c41238155c56fe8c4ca3e8f3d6bc99a8be117eb9aced2141435c855ce8be9bade575ce97af0d5ba8939268ba40d798 + languageName: node + linkType: hard + +"@types/hapi__mimos@npm:*": + version: 4.1.4 + resolution: "@types/hapi__mimos@npm:4.1.4" + dependencies: + "@types/mime-db": "*" + checksum: 8cae226b3d38427d3a380840506be0f226b0494d3e00826c2ff093e38e4f0ec2254d790531110f874b2ed6ac482eceaf5ac628a5e71898c49aea5d29a4875568 + languageName: node + linkType: hard + +"@types/hapi__shot@npm:*": + version: 4.1.6 + resolution: "@types/hapi__shot@npm:4.1.6" + dependencies: + "@types/node": "*" + checksum: 12fdb024a69890c0f552e5953c8afb76bf023c5315b8d70aeb9609c382efb63907a60ae5b048675c82fea5df9c3bad52befdef78df5758f6cf3d00b8cfee628d + languageName: node + linkType: hard + "@types/hast@npm:^2.0.0": version: 2.3.4 resolution: "@types/hast@npm:2.3.4" @@ -15907,6 +17290,13 @@ __metadata: languageName: node linkType: hard +"@types/http-assert@npm:*": + version: 1.5.5 + resolution: "@types/http-assert@npm:1.5.5" + checksum: cd6bb7fd42cc6e2a702cb55370b8b25231954ad74c04bcd185b943a74ded3d4c28099c30f77b26951df2426441baff41718816c60b5af80efe2b8888d900bf93 + languageName: node + linkType: hard + "@types/http-cache-semantics@npm:*": version: 4.0.0 resolution: "@types/http-cache-semantics@npm:4.0.0" @@ -15956,6 +17346,15 @@ __metadata: languageName: node linkType: hard +"@types/ioredis4@npm:@types/ioredis@^4.28.10": + version: 4.28.10 + resolution: "@types/ioredis@npm:4.28.10" + dependencies: + "@types/node": "*" + checksum: 0f2788cf25f490d3b345db8c5f8b8ce3f6c92cc99abcf744c8f974f02b9b3875233b3d22098614c462a0d6c41c523bd655509418ea88eb6249db6652290ce7cf + languageName: node + linkType: hard + "@types/is-glob@npm:^4.0.2": version: 4.0.4 resolution: "@types/is-glob@npm:4.0.4" @@ -16090,6 +17489,13 @@ __metadata: languageName: node linkType: hard +"@types/keygrip@npm:*": + version: 1.0.6 + resolution: "@types/keygrip@npm:1.0.6" + checksum: d157f60bf920492347791d2b26d530d5069ce05796549fbacd4c24d66ffbebbcb0ab67b21e7a1b80a593b9fd4b67dc4843dec04c12bbc2e0fddfb8577a826c41 + languageName: node + linkType: hard + "@types/keyv@npm:*, @types/keyv@npm:^3.1.1": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -16099,6 +17505,56 @@ __metadata: languageName: node linkType: hard +"@types/koa-compose@npm:*": + version: 3.2.8 + resolution: "@types/koa-compose@npm:3.2.8" + dependencies: + "@types/koa": "*" + checksum: 95c32bdee738ac7c10439bbf6342ca3b9f0aafd7e8118739eac7fb0fa703a23cfe4c88f63e13a69a16fbde702e0bcdc62b272aa734325fc8efa7e5625479752e + languageName: node + linkType: hard + +"@types/koa@npm:*": + version: 2.15.0 + resolution: "@types/koa@npm:2.15.0" + dependencies: + "@types/accepts": "*" + "@types/content-disposition": "*" + "@types/cookies": "*" + "@types/http-assert": "*" + "@types/http-errors": "*" + "@types/keygrip": "*" + "@types/koa-compose": "*" + "@types/node": "*" + checksum: f429b92f36f96c8f5ceb5333f982400d0db20e177b7d89a7a576ac6f63aff8c964f7ab313e2e281a07bbb93931c66327fb42614cd4984b2ef33dfe7cbd76d741 + languageName: node + linkType: hard + +"@types/koa@npm:2.14.0": + version: 2.14.0 + resolution: "@types/koa@npm:2.14.0" + dependencies: + "@types/accepts": "*" + "@types/content-disposition": "*" + "@types/cookies": "*" + "@types/http-assert": "*" + "@types/http-errors": "*" + "@types/keygrip": "*" + "@types/koa-compose": "*" + "@types/node": "*" + checksum: 57d809e42350c9ddefa2150306355e40757877468bb027e0bd99f5aeb43cfaf8ba8b14761ea65e419d6fb4c2403a1f3ed0762872a9cf040dbd14357caca56548 + languageName: node + linkType: hard + +"@types/koa__router@npm:12.0.3": + version: 12.0.3 + resolution: "@types/koa__router@npm:12.0.3" + dependencies: + "@types/koa": "*" + checksum: e9cdc53e01a6b2340583e94982cec2720c2d4c582240438eca57db7db4596f707578ac3e32cd32ace787331de304b6292cca8c98b0233c77f8749493c4991c96 + languageName: node + linkType: hard + "@types/ldapjs@npm:^2.2.5": version: 2.2.5 resolution: "@types/ldapjs@npm:2.2.5" @@ -16159,6 +17615,22 @@ __metadata: languageName: node linkType: hard +"@types/memcached@npm:^2.2.6": + version: 2.2.10 + resolution: "@types/memcached@npm:2.2.10" + dependencies: + "@types/node": "*" + checksum: c95e2ed494d5df5e45bab024d24ff2ba45930eb9737cb86564a5ac2a0b3fb5dfdc23d8a65061da38ffe2aabe202a8d333764c0c3dc99d2bb205bff8ba620f2c2 + languageName: node + linkType: hard + +"@types/mime-db@npm:*": + version: 1.43.5 + resolution: "@types/mime-db@npm:1.43.5" + checksum: 83a994ba20d5e1f5ad7bf9d408dd01631ce80d0bfdedabac5af046810f5d6e94b6d9f34bcbad85c2e02516851c946e034ba4122d4f5168b30a008fc19c2292fe + languageName: node + linkType: hard + "@types/mime-types@npm:^2.1.0": version: 2.1.4 resolution: "@types/mime-types@npm:2.1.4" @@ -16235,6 +17707,15 @@ __metadata: languageName: node linkType: hard +"@types/mysql@npm:2.15.22": + version: 2.15.22 + resolution: "@types/mysql@npm:2.15.22" + dependencies: + "@types/node": "*" + checksum: 325120f027b04052b3ed056fef096d186ecc0988d9efe110a52bd3f2233d02e17fb802ea42da7fa1ae1d150b0194cddf56ff71bfb28411bc05361f947b0635af + languageName: node + linkType: hard + "@types/ndjson@npm:^2.0.1": version: 2.0.4 resolution: "@types/ndjson@npm:2.0.4" @@ -16437,7 +17918,16 @@ __metadata: languageName: node linkType: hard -"@types/pg@npm:^8.6.6": +"@types/pg-pool@npm:2.0.4": + version: 2.0.4 + resolution: "@types/pg-pool@npm:2.0.4" + dependencies: + "@types/pg": "*" + checksum: 5ae1c49fe1820ec011f8e2a877198a62f4c9795d2cc340dff4527c26f24ee22dffe99a8ca5cdec6edb54613bded820cc51256fb668e0eb4d22794181b94fad82 + languageName: node + linkType: hard + +"@types/pg@npm:*, @types/pg@npm:^8.6.6": version: 8.11.6 resolution: "@types/pg@npm:8.11.6" dependencies: @@ -16448,6 +17938,17 @@ __metadata: languageName: node linkType: hard +"@types/pg@npm:8.6.1": + version: 8.6.1 + resolution: "@types/pg@npm:8.6.1" + dependencies: + "@types/node": "*" + pg-protocol: "*" + pg-types: ^2.2.0 + checksum: a44710ff06e70f57685ddb88edbb93d4b46e03fed90619f09853ed3868ab28541c4da03eccf6b0b444a7566a0b3c56028543ced43554d51168ca3f8ae15e194f + languageName: node + linkType: hard + "@types/picomatch@npm:2.3.3": version: 2.3.3 resolution: "@types/picomatch@npm:2.3.3" @@ -16759,6 +18260,13 @@ __metadata: languageName: node linkType: hard +"@types/shimmer@npm:^1.0.2": + version: 1.0.5 + resolution: "@types/shimmer@npm:1.0.5" + checksum: f6b0c950dc9187464c5393faf4f4e2b7b44b16665bb49196da28affecceb4fdcd9749af15cbe50f1a2de39f3a84b7523e73445f117f6b48bdbd61b892568364a + languageName: node + linkType: hard + "@types/sinon@npm:^10.0.10": version: 10.0.13 resolution: "@types/sinon@npm:10.0.13" @@ -16889,6 +18397,15 @@ __metadata: languageName: node linkType: hard +"@types/tedious@npm:^4.0.10": + version: 4.0.14 + resolution: "@types/tedious@npm:4.0.14" + dependencies: + "@types/node": "*" + checksum: 88505dda8b8e57e1da58ce74fb29bc2b4d64d90e9c34dc1d4b4010116b9785e23ce43f1e8016901bd27037e17d9d148e34d4ebd5f57d060212847e0df91cf024 + languageName: node + linkType: hard + "@types/tern@npm:*": version: 0.23.4 resolution: "@types/tern@npm:0.23.4" @@ -20157,10 +21674,10 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.0.0": - version: 1.2.2 - resolution: "cjs-module-lexer@npm:1.2.2" - checksum: 977f3f042bd4f08e368c890d91eecfbc4f91da0bc009a3c557bc4dfbf32022ad1141244ac1178d44de70fc9f3dea7add7cd9a658a34b9fae98a55d8f92331ce5 +"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.2.2": + version: 1.3.1 + resolution: "cjs-module-lexer@npm:1.3.1" + checksum: 75f20ac264a397ea5c63f9c2343a51ab878043666468f275e94862f7180ec1d764a400ec0c09085dcf0db3193c74a8b571519abd2bf4be0d2be510d1377c8d4b languageName: node linkType: hard @@ -24223,9 +25740,6 @@ __metadata: "@backstage/plugin-techdocs-backend": "workspace:^" "@gitbeaker/node": ^35.1.0 "@octokit/rest": ^19.0.3 - "@opentelemetry/api": ^1.4.1 - "@opentelemetry/exporter-prometheus": ^0.50.0 - "@opentelemetry/sdk-metrics": ^1.13.0 "@types/dockerode": ^3.3.0 "@types/express": ^4.17.6 "@types/express-serve-static-core": ^4.17.5 @@ -24282,6 +25796,9 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-techdocs-backend": "workspace:^" + "@opentelemetry/auto-instrumentations-node": ^0.43.0 + "@opentelemetry/exporter-prometheus": ^0.50.0 + "@opentelemetry/sdk-node": ^0.50.0 languageName: unknown linkType: soft @@ -25483,7 +27000,7 @@ __metadata: languageName: node linkType: hard -"gcp-metadata@npm:^6.1.0": +"gcp-metadata@npm:^6.0.0, gcp-metadata@npm:^6.1.0": version: 6.1.0 resolution: "gcp-metadata@npm:6.1.0" dependencies: @@ -26955,6 +28472,18 @@ __metadata: languageName: node linkType: hard +"import-in-the-middle@npm:1.7.1": + version: 1.7.1 + resolution: "import-in-the-middle@npm:1.7.1" + dependencies: + acorn: ^8.8.2 + acorn-import-assertions: ^1.9.0 + cjs-module-lexer: ^1.2.2 + module-details-from-path: ^1.0.3 + checksum: 37cc8c75fb7eac60611bafafea7fc60f794d0931fdabcec516c8a26effe69e914b1f7e8116e98549c6fdd1fe88dcaebfdebf35d7f52c761b48b312e40f3bf323 + languageName: node + linkType: hard + "import-lazy@npm:^2.1.0": version: 2.1.0 resolution: "import-lazy@npm:2.1.0" @@ -28750,6 +30279,19 @@ __metadata: languageName: node linkType: hard +"joi@npm:^17.3.0": + version: 17.13.1 + resolution: "joi@npm:17.13.1" + dependencies: + "@hapi/hoek": ^9.3.0 + "@hapi/topo": ^5.1.0 + "@sideway/address": ^4.1.5 + "@sideway/formula": ^3.0.1 + "@sideway/pinpoint": ^2.0.0 + checksum: e755140446a0e0fb679c0f512d20dfe1625691de368abe8069507c9bccae5216b5bb56b5a83100a600808b1753ab44fdfdc9933026268417f84b6e0832a9604e + languageName: node + linkType: hard + "join-component@npm:^1.1.0": version: 1.1.0 resolution: "join-component@npm:1.1.0" @@ -31824,6 +33366,13 @@ __metadata: languageName: node linkType: hard +"module-details-from-path@npm:^1.0.3": + version: 1.0.3 + resolution: "module-details-from-path@npm:1.0.3" + checksum: 378a8a26013889aa3086bfb0776b7860c5bb957336253e1ba5d779c2f239a218930b145ca76e52c1dd7c8079d52b2af64b8eec30822f81ffdb0dfa27d6fe6f33 + languageName: node + linkType: hard + "moo@npm:^0.5.0": version: 0.5.2 resolution: "moo@npm:0.5.2" @@ -34092,7 +35641,7 @@ __metadata: languageName: node linkType: hard -"pg-types@npm:^2.1.0": +"pg-types@npm:^2.1.0, pg-types@npm:^2.2.0": version: 2.2.0 resolution: "pg-types@npm:2.2.0" dependencies: @@ -35242,7 +36791,7 @@ __metadata: languageName: node linkType: hard -"protobufjs@npm:^7.0.0, protobufjs@npm:^7.2.4, protobufjs@npm:^7.2.5, protobufjs@npm:^7.2.6": +"protobufjs@npm:^7.0.0, protobufjs@npm:^7.2.3, protobufjs@npm:^7.2.5, protobufjs@npm:^7.2.6": version: 7.2.6 resolution: "protobufjs@npm:7.2.6" dependencies: @@ -36843,6 +38392,17 @@ __metadata: languageName: node linkType: hard +"require-in-the-middle@npm:^7.1.1": + version: 7.3.0 + resolution: "require-in-the-middle@npm:7.3.0" + dependencies: + debug: ^4.1.1 + module-details-from-path: ^1.0.3 + resolve: ^1.22.1 + checksum: 014ae8aef4a0ed995476d0ba6f7d86afff7114247353894d3b41ef7b0953de03303c30ad127eaac4036eb0c8c862fd247b760e2a6de10ac147712372304e3e73 + languageName: node + linkType: hard + "require-main-filename@npm:^2.0.0": version: 2.0.0 resolution: "require-main-filename@npm:2.0.0" @@ -37720,7 +39280,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.6.0 resolution: "semver@npm:7.6.0" dependencies: @@ -37993,6 +39553,13 @@ __metadata: languageName: node linkType: hard +"shimmer@npm:^1.2.1": + version: 1.2.1 + resolution: "shimmer@npm:1.2.1" + checksum: aa0d6252ad1c682a4fdfda69e541be987f7a265ac7b00b1208e5e48cc68dc55f293955346ea4c71a169b7324b82c70f8400b3d3d2d60b2a7519f0a3522423250 + languageName: node + linkType: hard + "short-unique-id@npm:^5.0.2": version: 5.0.3 resolution: "short-unique-id@npm:5.0.3" From 5fa3bb2ef9be40e02fb78e0406a1d2333a959234 Mon Sep 17 00:00:00 2001 From: Ishwarya Surendrababu Date: Tue, 7 May 2024 14:06:10 +0530 Subject: [PATCH 275/567] Updating the name Signed-off-by: Ishwarya Surendrababu --- microsite/data/plugins/digital.ai-deploy.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsite/data/plugins/digital.ai-deploy.yaml b/microsite/data/plugins/digital.ai-deploy.yaml index 3c45330449..6077f59fa7 100644 --- a/microsite/data/plugins/digital.ai-deploy.yaml +++ b/microsite/data/plugins/digital.ai-deploy.yaml @@ -1,11 +1,11 @@ --- -title: digital.ai Deploy +title: Digital.ai Deploy author: digital.ai authorUrl: https://digital.ai/ category: CI/CD description: The plugin offers integration with Digital.ai Deploy and backstage components and services. It provide access to deployments and reports. documentation: https://docs.digital.ai/bundle/devops-deploy-version-v.24.1/page/deploy/concept/xl-deploy-backstage-overview.html -iconUrl: /img/digital-ai-deploy.svg +iconUrl: /img/digital.ai-deploy.svg npmPackageName: '@digital.ai/plugin-dai-deploy' tags: - ci From dff1455416d1b2b113b325afe7e6a1ee7af91e41 Mon Sep 17 00:00:00 2001 From: Santosh Bharadwaj Rangavajjula Date: Tue, 7 May 2024 11:03:15 +0200 Subject: [PATCH 276/567] Update ADOPTERS.md Added Scania into adopters list Signed-off-by: Santosh Bharadwaj Rangavajjula --- ADOPTERS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 3878563683..2a5c587270 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -268,3 +268,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Aurora Innovation](https://aurorainnovation.com) | [@O5ten](https://github.com/O5ten) | Heavy usage of scaffolder, techdocs, homepage, k8s plugin and homegrown plugins to track migration paths and so on within a developer portal. It acts as a starting point for new and old developers to find all of our internal tooling in one place. | [ENSEK](https://ensek.com/) | [Timothy Deakin](https://github.com/cftad) |We are using Backstage as our internal developer portal to provide a single pane of glass for our developers to access all the tools and services they need to build and maintain our software. | | [OP Financial Group](https://www.op.fi/op-financial-group) | [Heikki Hellgren](https://github.com/drodil), [Jyrki Koistinen](https://github.com/snyvision) | We are using Backstage as a gateway into our internal development platform offering to simplify complexity. | +| [Scania](https://www.scania.com) | [Santosh Rangavajjula](https://linkedin.com/in/rsbth) | We are implementing backstage at Scania to consolidate operational information from Gitlab, Jira, Confluence, Artifactory, Servicenow and other tools into one place. | + From 9cdcf52c249d57e24307773162e84e4941b1126a Mon Sep 17 00:00:00 2001 From: Shaked Braimok Yosef Date: Tue, 7 May 2024 12:20:21 +0300 Subject: [PATCH 277/567] Update ADOPTERS.md Signed-off-by: Shaked Braimok Yosef --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 3878563683..7c4d3566a5 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -268,3 +268,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Aurora Innovation](https://aurorainnovation.com) | [@O5ten](https://github.com/O5ten) | Heavy usage of scaffolder, techdocs, homepage, k8s plugin and homegrown plugins to track migration paths and so on within a developer portal. It acts as a starting point for new and old developers to find all of our internal tooling in one place. | [ENSEK](https://ensek.com/) | [Timothy Deakin](https://github.com/cftad) |We are using Backstage as our internal developer portal to provide a single pane of glass for our developers to access all the tools and services they need to build and maintain our software. | | [OP Financial Group](https://www.op.fi/op-financial-group) | [Heikki Hellgren](https://github.com/drodil), [Jyrki Koistinen](https://github.com/snyvision) | We are using Backstage as a gateway into our internal development platform offering to simplify complexity. | +| [Senora.dev](https://senora.dev) | [Shaked Braimok Yosef](https://github.com/ShakedBraimok) | We are using Backstage as a service catalog for our costumers. | From 3d871b99f9ac7668c37bd8e5c84926afd923972d Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 7 May 2024 11:23:14 +0200 Subject: [PATCH 278/567] chore: move to private method over gettter Signed-off-by: blam --- plugins/scaffolder-backend/api-report.md | 2 -- .../src/scaffolder/tasks/StorageTaskBroker.ts | 22 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 0c01fd6a9e..8caa0ab0d4 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -572,8 +572,6 @@ export class TaskManager implements TaskContext_2 { // (undocumented) getWorkspaceName(): Promise; // (undocumented) - get isWorkspaceSerializationEnabled(): boolean; - // (undocumented) rehydrateWorkspace?(options: { taskId: string; targetPath: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 9f8f7df241..03a2833938 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -88,14 +88,6 @@ export class TaskManager implements TaskContext { private readonly config?: Config, ) {} - get isWorkspaceSerializationEnabled(): boolean { - return ( - this.config?.getOptionalBoolean( - 'scaffolder.EXPERIMENTAL_workspaceSerialization', - ) ?? false - ); - } - get spec() { return this.task.spec; } @@ -120,7 +112,7 @@ export class TaskManager implements TaskContext { taskId: string; targetPath: string; }): Promise { - if (this.isWorkspaceSerializationEnabled) { + if (this.isWorkspaceSerializationEnabled()) { this.storage.rehydrateWorkspace?.(options); } } @@ -171,7 +163,7 @@ export class TaskManager implements TaskContext { } async serializeWorkspace?(options: { path: string }): Promise { - if (this.isWorkspaceSerializationEnabled) { + if (this.isWorkspaceSerializationEnabled()) { await this.storage.serializeWorkspace?.({ path: options.path, taskId: this.task.taskId, @@ -180,7 +172,7 @@ export class TaskManager implements TaskContext { } async cleanWorkspace?(): Promise { - if (this.isWorkspaceSerializationEnabled) { + if (this.isWorkspaceSerializationEnabled()) { await this.storage.cleanWorkspace?.({ taskId: this.task.taskId }); } } @@ -219,6 +211,14 @@ export class TaskManager implements TaskContext { }, 1000); } + private isWorkspaceSerializationEnabled(): boolean { + return ( + this.config?.getOptionalBoolean( + 'scaffolder.EXPERIMENTAL_workspaceSerialization', + ) ?? false + ); + } + async getInitiatorCredentials(): Promise { const secrets = this.task.secrets as InternalTaskSecrets; From a4b399f18036011122538dac1008a7c2ca3092d3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 7 May 2024 11:27:54 +0200 Subject: [PATCH 279/567] docs: remove developer journey Signed-off-by: Patrik Oldsberg --- docs/tutorials/journey.md | 271 -------------------------------------- microsite/sidebars.json | 1 - mkdocs.yml | 1 - 3 files changed, 273 deletions(-) delete mode 100644 docs/tutorials/journey.md diff --git a/docs/tutorials/journey.md b/docs/tutorials/journey.md deleted file mode 100644 index af78cbe597..0000000000 --- a/docs/tutorials/journey.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -id: journey -title: Future developer journey -description: This document describes a possible journey of a future Backstage ---- - -> This document describes a possible journey of a **_future_** Backstage plugin -> developer as they build a plugin that touches many different aspects of a -> Backstage. The story invents many new things that are not part of Backstage -> today, but are things that I'm suggesting we should add as long term or north -> star goals. The idea is to discuss what parts of the story makes sense to aim -> for, and what we'd want to do differently or not at all. The "chapters" are -> numbered to make it a bit easier to comment on parts of the story. - -# The Protagonist - -Sam is an experienced developer that has worked with Backstage for a while, and -knows the best practices and tools available to build plugins. Sam also likes -music and wants to have a theme tune for every service in Backstage. - -# The End - -Sam built a Spotify plugin for Backstage that allows service owners to define a -theme tune for their service. The theme tune plays whenever a user visits the -service page in Backstage. The plugin is published to npm and available for any -organization to easily install and add to their Backstage installation. - -# 1. A New Plugin - -Sam chooses to develop this plugin in a standalone project and creates a new -plugin using `npx @backstage/cli new --select plugin`, which detects that it's not -being run in an existing project and therefore creates a separate plugin repo. - -Spinning up the frontend with `yarn start`, Sam goes to work with getting the -base functionality of a Spotify web player going. By installing a couple of -dependencies and whipping together a nice UI, the player is pretty much done. - -# 2. The Auth Menace - -Sam realizes users need to be authenticated towards the Spotify API to be able -to play music, and Backstage doesn't support Spotify login yet. Sam adds the -`@backstage/plugin-auth-backend` as local development middleware in the project, -and provides the necessary wrapping logic and configuration for the -`passport-spotify` strategy. The Spotify auth provider is now available in the -local development backend, and by adding a frontend `SpotifyAuth` Utility API -that implements the `OAuthApi` type, it's now working in the frontend too. - -```ts -const spotifyAuthApiRef = createApiRef({ - id: 'core.auth.spotify', -}); -``` - -Sam realizes that Spotify auth might be useful to others, and that it would be -more convenient if it was a part of the Backstage Core. After submitting and -merging a Pull Request with the additions to the -`@backstage/plugin-auth-backend` and `@backstage/core-plugin-api` packages, -Spotify auth is now available for everyone to use. Since the Backstage Core team -also adds it to the public demo server, Sam can now get rid of it in the local -setup and rely on the shared development auth providers instead. - -The only thing left now is making sure that users of the plugin provide Spotify -auth in the app. Sam ensures this by adding `spotifyAuthApiRef` to the plugin's -list of required APIs, as well as listing it in the requirements section in the -README. - -```md -## Requirements - -This plugin requires the following APIs to function: - -- `spotifyAuthApiRef` from `@@backstage/core-plugin-api@^1.1.0` -``` - -# 3. The Catalog Awakens - -Sam now has a working player and a method for users to log in to listen to -music, but the goal is to provide theme songs for services. Sam adds this -functionality by defining a new metadata annotation called -`sam.wise/spotify-track-id`. The annotation's value is a Spotify track ID and -can be defined in a component like this: - -```yaml -apiVersion: backstage.io/v1 -kind: Component -metadata: - name: my-component - annotations: - sam.wise/spotify-track-id: '4uLU6hMCjMI75M1A2tKUQC' -spec: - type: service -``` - -Sam creates a JSON schema that documents the annotation and allows it to be used -in validation and documentation for organizations that choose to adopt the -plugin. - -```json -{ - "sam.wise/spotify-track-id": { - "$id": "https://raw.githubusercontent.com/sam/backstage-spotify-theme/master/annotation.json#/sam.wise/spotify-track-id", - "type": "string", - "title": "Spotify Track Annotation", - "description": "Spotify track ID to associated with the entity", - "examples": ["4uLU6hMCjMI75M1A2tKUQC"] - } -} -``` - -# 4. The Rise of Widgets - -Sam also wraps the music player in an entity page widget. This allows anyone -that wants to use the plugin to add the player to any of their entity layout -templates, which will make it show up for every entity of that kind. - -```tsx -export const PlayerWidget = plugin.createEntityWidget({ - component: WebPlayer, - locations: ['header', 'card', 'footer'], - cardSize: [2, 4], -}); -``` - -The widget receives information about the entity in whose page it's being -embedded, which makes it simple to grab the track id from the annotations and -hook up the player. - -Sam also modifies the standalone plugin development setup to include this new -widget inside a basic entity page, adding it to a couple of different places -where users of the plugin may want to put the player, just to make sure they all -work. - -# 5. The First User - -At this point the only things that anyone that wants to use Sam's plugin needs -to do are to add -https://raw.githubusercontent.com/sam/backstage-spotify-theme/master/annotation.json#/sam.wise/spotify-track-id -to their catalog schema, import and add the `PlayerWidget` on the desired entity -template pages, and make sure they're providing Spotify auth. - -Sam soon sees the first "Used by" show up on GitHub, and feedback starts rolling -in. Users really like the plugin, and some a requesting the possibility to -select a theme tune when creating a new component. Sam jumps on the idea and -adds a new creation hook that is exported by the plugin. The hook can be -installed either in a single, all, or component templates that match a label. It -adds a field as a part of the component creation process with a nice search box -that allows users to search for a track that they want to use as the theme tune. - -# 6. Return to the Repo - -Sam is pretty content at this point, but would like to make it easier for users -to change the track after creation, preferable using the same search box that -was made for the creation form. By adding an edit button to the `PlayerWidget`, -and a nice empty state, Sam is able to provide the appropriate hooks in the GUI -to open up a search dialog. - -To save the selected track, Sam uses the `RepoApi` to suggest a change to the -entity definition file. This will create a Pull Request for organizations that -use GitHub, a Merge Request for users of GitLab, and so on. - -```ts -const repoApi = useApi(repoApiRef); -const alertApi = useApi(alertApiRef); - -const onSave = async () => { - const { url } = await repoApi.createChangeRequest({ - title: `Change theme tune to ${track.title} by ${track.artist}`, - changes: [ - { - path: entityYamlPath, - content: newEntityYamlContent, - }, - ], - }); - - alertApi.post({ message: `Requested change, ${url}` }); -}; -``` - -Now it's much simpler for users to change the theme tune, as they no longer need -to go look up a track ID and edit a YAML file. Instead, they can now stay inside -Backstage and search for the track and request the change from there. In -addition, the requested change can be reviewed by the regular process of each -organization. - -# 7. The User Strikes Back - -Sam's plugin is pretty popular at this point, and has been picked up and used by -many organizations. But some users start voicing concerns that they have too -many different hand-crafted annotations in their entity descriptions, and would -like to be able to avoid some of them. They really like the theme tunes though, -and wish they could keep them without having to put them in the entity -description, even if that means it won't go through the regular source control -review process. - -One day Sam receives a Pull Request for the plugin. It adds an option to use the -Database provided by `@backstage/backend-common` to store the track ID. It's all -packaged into a new backend plugin that will also extend the catalog backend -with schema and functionality to automatically load the value of the -`sam.wise/spotify-track-id` annotation from the backend plugin and database. The -backend plugin also extends the common GraphQL schema with a mutation that -updates the track ID in the database. - -On the frontend the Pull Request doesn't change much. It defines the save action -the was previously using the `RepoApi` in its own API. - -```ts -type ThemeTuneStorageApi = { - save(entity: Entity, trackId: string): Promise; -}; -``` - -The plugin also provides two different implementations of the API, one that uses -the old behavior of the `RepoApi`, and a new one that calls the `GraphQL` API. -The new API relies on the `IdentityApi` as a mechanism for authorizing changes, -instead of source control reviews. The `IdentityApi` provides a token that is -included in the request to the backend, which then must match the owner of the -component for which the user is trying to change the theme tune. - -> Author breaking the 4th wall here. I actually think every GraphQL request -> should include the ID token of the user, but invented a reason to include it -> here anyway. - -The API is selected based on a configuration parameter for the plugin, but -defaults to the original `RepoApi` behavior. - -```ts -if (config.getBoolean('storeTrackInDatabase')) { - return new GraphQLThemeTuneStore(graphqlClient, identityApi, alertApi); -} else { - return new RepoThemeTuneStore(repoApi, alertApi); -} -``` - -Sam is amazed by the pure awesomeness of this change, replies with a "👍" and -hits merge. - -# 8. Attack of the Clones - -Sam just released v1.8.4 of the plugin, and at this point it's so popular that a -couple of other plugins have started depending on the -`sam.wise/spotify-track-id` annotation. One such plugin being the -`spotify-album-art` plugin that can display the album art of the theme tune as -the background of the entity header. Sam thinks it's all pretty cool, but -doesn't like that the annotation that was once an internal concern of the plugin -is now becoming a standard in the community. - -In order to standardize the annotation in Backstage, Sam submits a Pull Request -to the Backstage Core repo. The request suggests a new well-known metadata -annotation called `spotify.com/track-id`, with the same schema definition as -Sam's label, and refers to Sam's own plugin and the `spotify-album-art` plugin -as existing usages. The Backstage maintainers merge the Pull Request, after -checking with the folks over at Spotify that they're cool with the annotation, -and faffing about over some minor grammar mistake in the annotation description. - -With the annotation now available inside Backstage Core, Sam releases v2 of the -plugin, which uses the new annotation. It can still consume the old annotation -for backwards compatibility, but new users of the plugin no longer need to add -the -https://raw.githubusercontent.com/sam/backstage-spotify-theme/master/annotation.json#/sam.wise/spotify-track-id -extension to their catalog schema, as it's now part of the core schema. The new -release of Sam's plugin specifies a dependency on Backstage with a minimum -version set to the same release as the one were the annotation was added to the -core schema. - -# 9. Revenge of the Sam - -Sam, now in full control of all theme tunes in Backstage, releases v2.0.1, which -switches all tracks to 4uLU6hMCjMI75M1A2tKUQC. Sam wanted to do something more -nefarious, but since Backstage sandboxes sensitive actions and is mostly -read-only with strict CSP, Sam's hands were tied. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index eadcbbf806..6d9ba3a680 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -498,7 +498,6 @@ "api/deprecations" ], "Tutorials": [ - "tutorials/journey", "tutorials/quickstart-app-plugin", "tutorials/react-router-stable-migration", "tutorials/react18-migration", diff --git a/mkdocs.yml b/mkdocs.yml index 7ba6fdfefa..66ce7d8def 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -195,7 +195,6 @@ nav: - Utility APIs: 'api/utility-apis.md' - Deprecations: 'api/deprecations.md' - Tutorials: - - Future developer journey: 'tutorials/journey.md' - React Router 6.0 Migration: 'tutorials/react-router-stable-migration.md' - Package Role Migration: 'tutorials/package-role-migration.md' - Migrating away from @backstage/core: 'tutorials/migrating-away-from-core.md' From 845d56a76fe9e2131aaaf8221b438204976c1dbd Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 2 May 2024 09:59:37 +0300 Subject: [PATCH 280/567] feat: improve signal lifecycle management + server side pinging Signed-off-by: Heikki Hellgren --- .changeset/healthy-shirts-roll.md | 5 ++ .../backend-legacy/src/plugins/signals.ts | 1 + plugins/signals-backend/api-report.md | 6 ++ plugins/signals-backend/package.json | 3 +- plugins/signals-backend/src/plugin.ts | 6 ++ .../src/service/SignalManager.test.ts | 20 +++++++ .../src/service/SignalManager.ts | 60 +++++++++++++++++-- .../src/service/router.test.ts | 2 + plugins/signals-backend/src/service/router.ts | 26 ++++---- yarn.lock | 11 ++-- 10 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 .changeset/healthy-shirts-roll.md diff --git a/.changeset/healthy-shirts-roll.md b/.changeset/healthy-shirts-roll.md new file mode 100644 index 0000000000..aec74a1181 --- /dev/null +++ b/.changeset/healthy-shirts-roll.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-signals-backend': patch +--- + +Improved signal lifecycle management and added server side pinging of connections diff --git a/packages/backend-legacy/src/plugins/signals.ts b/packages/backend-legacy/src/plugins/signals.ts index d97d3fc170..33b1af5edf 100644 --- a/packages/backend-legacy/src/plugins/signals.ts +++ b/packages/backend-legacy/src/plugins/signals.ts @@ -25,5 +25,6 @@ export default async function createPlugin( events: env.events, identity: env.identity, discovery: env.discovery, + config: env.config, }); } diff --git a/plugins/signals-backend/api-report.md b/plugins/signals-backend/api-report.md index cad43bfaae..d59ca8e5ad 100644 --- a/plugins/signals-backend/api-report.md +++ b/plugins/signals-backend/api-report.md @@ -5,9 +5,11 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; import { EventsService } from '@backstage/plugin-events-node'; import express from 'express'; import { IdentityApi } from '@backstage/plugin-auth-node'; +import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { UserInfoService } from '@backstage/backend-plugin-api'; @@ -20,12 +22,16 @@ export interface RouterOptions { // (undocumented) auth?: AuthService; // (undocumented) + config: Config; + // (undocumented) discovery: PluginEndpointDiscovery; // (undocumented) events: EventsService; // (undocumented) identity: IdentityApi; // (undocumented) + lifecycle?: LifecycleService; + // (undocumented) logger: LoggerService; // (undocumented) userInfo?: UserInfoService; diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 5d961c71ef..24561caf46 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -41,7 +41,7 @@ "node-fetch": "^2.6.7", "uuid": "^9.0.0", "winston": "^3.2.1", - "ws": "^8.14.2", + "ws": "^8.17.0", "yn": "^4.0.0" }, "devDependencies": { @@ -51,6 +51,7 @@ "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", "@backstage/plugin-events-backend": "workspace:^", "@types/supertest": "^2.0.8", + "@types/ws": "^8.5.10", "msw": "^1.0.0", "supertest": "^6.2.4" }, diff --git a/plugins/signals-backend/src/plugin.ts b/plugins/signals-backend/src/plugin.ts index 3e00c00a34..0963b79ccb 100644 --- a/plugins/signals-backend/src/plugin.ts +++ b/plugins/signals-backend/src/plugin.ts @@ -32,6 +32,8 @@ export const signalsPlugin = createBackendPlugin({ deps: { httpRouter: coreServices.httpRouter, logger: coreServices.logger, + config: coreServices.rootConfig, + lifecycle: coreServices.rootLifecycle, identity: coreServices.identity, discovery: coreServices.discovery, userInfo: coreServices.userInfo, @@ -41,6 +43,8 @@ export const signalsPlugin = createBackendPlugin({ async init({ httpRouter, logger, + config, + lifecycle, identity, discovery, userInfo, @@ -50,7 +54,9 @@ export const signalsPlugin = createBackendPlugin({ httpRouter.use( await createRouter({ logger, + config, identity, + lifecycle, discovery, userInfo, auth, diff --git a/plugins/signals-backend/src/service/SignalManager.test.ts b/plugins/signals-backend/src/service/SignalManager.test.ts index 3ee99bb34b..7d3a6ddf59 100644 --- a/plugins/signals-backend/src/service/SignalManager.test.ts +++ b/plugins/signals-backend/src/service/SignalManager.test.ts @@ -17,6 +17,7 @@ import { WebSocket } from 'ws'; import { EventsServiceSubscribeOptions } from '@backstage/plugin-events-node'; import { SignalManager } from './SignalManager'; import { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; class MockWebSocket { closed: boolean = false; @@ -30,6 +31,11 @@ class MockWebSocket { this.closed = true; } + terminate(): void { + this.readyState = WebSocket.CLOSED; + this.closed = true; + } + on( event: string | symbol, listener: (this: WebSocket, ...args: any[]) => void, @@ -63,9 +69,23 @@ describe('SignalManager', () => { }, }; + const shutdownHooks: Function[] = []; + const mockLifecycle = { + addShutdownHook: (hook: Function) => shutdownHooks.push(hook), + }; + const manager = SignalManager.create({ events: mockEvents, logger: getVoidLogger(), + config: new ConfigReader({}), + lifecycle: mockLifecycle as any, + }); + + it('should close all connections when server is closed', () => { + const ws = new MockWebSocket(); + manager.addConnection(ws as unknown as WebSocket); + shutdownHooks.forEach(hook => hook()); + expect(ws.closed).toBeTruthy(); }); it('should close connection on error', () => { diff --git a/plugins/signals-backend/src/service/SignalManager.ts b/plugins/signals-backend/src/service/SignalManager.ts index 3211c51c9c..995c80ad28 100644 --- a/plugins/signals-backend/src/service/SignalManager.ts +++ b/plugins/signals-backend/src/service/SignalManager.ts @@ -20,8 +20,10 @@ import { v4 as uuid } from 'uuid'; import { JsonObject } from '@backstage/types'; import { BackstageUserInfo, + LifecycleService, LoggerService, } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; /** * @internal @@ -32,6 +34,7 @@ export type SignalConnection = { ws: WebSocket; ownershipEntityRefs: string[]; subscriptions: Set; + isAlive: boolean; }; /** @@ -39,7 +42,9 @@ export type SignalConnection = { */ export type SignalManagerOptions = { events: EventsService; + config: Config; logger: LoggerService; + lifecycle?: LifecycleService; }; /** @internal */ @@ -50,6 +55,7 @@ export class SignalManager { >(); private events: EventsService; private logger: LoggerService; + private pingInterval: ReturnType | undefined; static create(options: SignalManagerOptions) { return new SignalManager(options); @@ -64,11 +70,43 @@ export class SignalManager { onEvent: (params: EventParams) => this.onEventBrokerEvent(params.eventPayload as SignalPayload), }); + + options.lifecycle?.addShutdownHook(() => this.onShutdown()); + } + + private ping() { + this.connections.forEach(conn => { + if (!conn.isAlive) { + this.logger.debug(`Connection ${conn.id} is not alive, terminating`); + conn.ws.terminate(); + return; + } + + conn.isAlive = false; + conn.ws.ping(); + }); + } + + private onShutdown() { + if (this.pingInterval) { + clearInterval(this.pingInterval); + } + + // TODO: Unsubscribe from events? + + this.connections.forEach(conn => { + conn.ws.terminate(); + }); + this.connections.clear(); } addConnection(ws: WebSocket, identity?: BackstageUserInfo) { - const id = uuid(); + // Start pinging on first connection + if (!this.pingInterval) { + this.pingInterval = setInterval(() => this.ping(), 30000); + } + const id = uuid(); const conn = { id, user: identity?.userEntityRef ?? 'user:default/guest', @@ -77,25 +115,37 @@ export class SignalManager { 'user:default/guest', ], subscriptions: new Set(), + isAlive: true, }; this.connections.set(id, conn); + this.logger.debug(`Connection ${id} connected`); ws.on('error', (err: Error) => { this.logger.error( `Error occurred with connection ${id}: ${err}, closing connection`, ); - ws.close(); + ws.terminate(); this.connections.delete(id); }); ws.on('close', (code: number, reason: Buffer) => { - this.logger.info( + this.logger.debug( `Connection ${id} closed with code ${code}, reason: ${reason}`, ); + ws.terminate(); this.connections.delete(id); }); + ws.on('ping', () => { + conn.isAlive = true; + ws.pong(); + }); + + ws.on('pong', () => { + conn.isAlive = true; + }); + ws.on('message', (data: RawData, isBinary: boolean) => { this.logger.debug(`Received message from connection ${id}: ${data}`); if (isBinary) { @@ -114,12 +164,12 @@ export class SignalManager { private handleMessage(connection: SignalConnection, message: JsonObject) { if (message.action === 'subscribe' && message.channel) { - this.logger.info( + this.logger.debug( `Connection ${connection.id} subscribed to ${message.channel}`, ); connection.subscriptions.add(message.channel as string); } else if (message.action === 'unsubscribe' && message.channel) { - this.logger.info( + this.logger.debug( `Connection ${connection.id} unsubscribed from ${message.channel}`, ); connection.subscriptions.delete(message.channel as string); diff --git a/plugins/signals-backend/src/service/router.test.ts b/plugins/signals-backend/src/service/router.test.ts index bbcf506d19..c081031c54 100644 --- a/plugins/signals-backend/src/service/router.test.ts +++ b/plugins/signals-backend/src/service/router.test.ts @@ -24,6 +24,7 @@ import { createRouter } from './router'; import { EventsService } from '@backstage/plugin-events-node'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { UserInfoService } from '@backstage/backend-plugin-api'; +import { ConfigReader } from '@backstage/config'; const eventsServiceMock: jest.Mocked = { subscribe: jest.fn(), @@ -53,6 +54,7 @@ describe('createRouter', () => { events: eventsServiceMock, discovery, userInfo, + config: new ConfigReader({}), }); app = express().use(router); }); diff --git a/plugins/signals-backend/src/service/router.ts b/plugins/signals-backend/src/service/router.ts index fab706e878..ea706ebf1a 100644 --- a/plugins/signals-backend/src/service/router.ts +++ b/plugins/signals-backend/src/service/router.ts @@ -23,6 +23,7 @@ import Router from 'express-promise-router'; import { AuthService, BackstageUserInfo, + LifecycleService, LoggerService, UserInfoService, } from '@backstage/backend-plugin-api'; @@ -33,6 +34,7 @@ import { IdentityApi } from '@backstage/plugin-auth-node'; import { EventsService } from '@backstage/plugin-events-node'; import { WebSocket, WebSocketServer } from 'ws'; import { Duplex } from 'stream'; +import { Config } from '@backstage/config'; /** @public */ export interface RouterOptions { @@ -40,6 +42,8 @@ export interface RouterOptions { events: EventsService; identity: IdentityApi; discovery: PluginEndpointDiscovery; + config: Config; + lifecycle?: LifecycleService; auth?: AuthService; userInfo?: UserInfoService; } @@ -56,16 +60,12 @@ export async function createRouter( let apiUrl: string | undefined = undefined; const webSocketServer = new WebSocketServer({ - noServer: true, - clientTracking: false, + noServer: true, // handle upgrade manually + clientTracking: false, // handle connections in SignalManager }); webSocketServer.on('error', (error: Error) => { - logger.error('WebSocket server error', error); - }); - - webSocketServer.on('close', () => { - logger.info('WebSocket server closed'); + logger.error(`WebSocket server error: ${error}`); }); const handleUpgrade = async ( @@ -94,7 +94,7 @@ export async function createRouter( } } } catch (e) { - logger.error('Failed to authenticate WebSocket connection', e); + logger.error(`Failed to authenticate WebSocket connection: ${e}`); socket.write( 'HTTP/1.1 401 Web Socket Protocol Handshake\r\n' + 'Upgrade: WebSocket\r\n' + @@ -115,7 +115,14 @@ export async function createRouter( }, ); } catch (e) { - logger.error('Failed to handle WebSocket upgrade', e); + logger.error(`Failed to handle WebSocket upgrade: ${e}`); + socket.write( + 'HTTP/1.1 500 Web Socket Protocol Handshake\r\n' + + 'Upgrade: WebSocket\r\n' + + 'Connection: Upgrade\r\n' + + '\r\n', + ); + socket.destroy(); } }; @@ -145,7 +152,6 @@ export async function createRouter( router.use(upgradeMiddleware); router.get('/health', (_, response) => { - logger.info('PONG!'); response.json({ status: 'ok' }); }); diff --git a/yarn.lock b/yarn.lock index 89d4b0ee45..f541bd1702 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7271,6 +7271,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": "*" "@types/supertest": ^2.0.8 + "@types/ws": ^8.5.10 express: ^4.17.1 express-promise-router: ^4.1.0 http-proxy-middleware: ^2.0.0 @@ -7279,7 +7280,7 @@ __metadata: supertest: ^6.2.4 uuid: ^9.0.0 winston: ^3.2.1 - ws: ^8.14.2 + ws: ^8.17.0 yn: ^4.0.0 languageName: unknown linkType: soft @@ -42139,9 +42140,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.8.0": - version: 8.16.0 - resolution: "ws@npm:8.16.0" +"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.17.0, ws@npm:^8.8.0": + version: 8.17.0 + resolution: "ws@npm:8.17.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -42150,7 +42151,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: feb3eecd2bae82fa8a8beef800290ce437d8b8063bdc69712725f21aef77c49cb2ff45c6e5e7fce622248f9c7abaee506bae0a9064067ffd6935460c7357321b + checksum: 147ef9eab0251364e1d2c55338ad0efb15e6913923ccbfdf20f7a8a6cb8f88432bcd7f4d8f66977135bfad35575644f9983201c1a361019594a4e53977bf6d4e languageName: node linkType: hard From c6113b5e787a569736dc82394debe756f84db2a0 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 7 May 2024 11:46:35 +0200 Subject: [PATCH 281/567] chore: added api-reports Signed-off-by: blam --- packages/backend-test-utils/api-report.md | 10 ++++++++++ plugins/events-node/api-report.md | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index ff3f50ef8c..4f6eb47243 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -17,6 +17,7 @@ import { BackstageUserPrincipal } from '@backstage/backend-plugin-api'; import { CacheService } from '@backstage/backend-plugin-api'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; +import { EventsService } from '@backstage/plugin-events-node'; import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; @@ -183,6 +184,15 @@ export namespace mockServices { partialImpl?: Partial | undefined, ) => ServiceMock; } + // (undocumented) + export namespace events { + const // (undocumented) + factory: () => ServiceFactory; + const // (undocumented) + mock: ( + partialImpl?: Partial | undefined, + ) => ServiceMock; + } export function httpAuth(options?: { pluginId?: string; defaultCredentials?: BackstageCredentials; diff --git a/plugins/events-node/api-report.md b/plugins/events-node/api-report.md index 9574d6c098..46646d2ce8 100644 --- a/plugins/events-node/api-report.md +++ b/plugins/events-node/api-report.md @@ -4,6 +4,7 @@ ```ts import { LoggerService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @public @@ -61,6 +62,12 @@ export interface EventsService { // @public (undocumented) export type EventsServiceEventHandler = (params: EventParams) => Promise; +// @public (undocumented) +export const eventsServiceFactory: () => ServiceFactory< + EventsService, + 'plugin' +>; + // @public export const eventsServiceRef: ServiceRef; From f612f630edf04b2bce43cb4a8dd7f3a93ae05a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 7 May 2024 12:40:26 +0200 Subject: [PATCH 282/567] docs updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/empty-spoons-tell.md | 5 ++++- docs/plugins/proxying.md | 4 ++++ plugins/proxy-backend/config.d.ts | 22 ++++++++++++++++------ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.changeset/empty-spoons-tell.md b/.changeset/empty-spoons-tell.md index 8c68cdc721..44e3dceb6f 100644 --- a/.changeset/empty-spoons-tell.md +++ b/.changeset/empty-spoons-tell.md @@ -36,7 +36,10 @@ There are three possible `credentials` settings at this point: The value `dangerously-allow-unauthenticated` was the old default. The value `require` is the new default, so requests that were previously -permitted may now start resulting in `401 Unauthorized` responses. +permitted may now start resulting in `401 Unauthorized` responses. If you have +`backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`, this does not +apply; the proxy will behave as if all endpoints were set to +`dangerously-allow-unauthenticated`. If you have proxy endpoints that require unauthenticated access still, please add `credentials: dangerously-allow-unauthenticated` to their declarations in diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 0f37eec982..84ef62ffe8 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -66,6 +66,10 @@ values: you also add `allowedHeaders: ['Authorization']` to an endpoint configuration, then the Backstage token (if provided) WILL be forwarded. +Note that if you have `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to +`true`, the `credentials` value does not apply; the proxy will behave as if all +endpoints were set to `dangerously-allow-unauthenticated`. + If the value is a string, it is assumed to correspond to: ```yaml diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 710ff6d23f..ccce1c2d42 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -88,6 +88,11 @@ export interface Config { * are required to access this proxy target. The target can still * apply its own credentials checks, but the proxy will not help * block non-Backstage-blessed callers. + * + * Note that if you have + * `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`, + * the `credentials` value does not apply; the proxy will behave as + * if all endpoints were set to `dangerously-allow-unauthenticated`. */ credentials?: | 'require' @@ -151,15 +156,20 @@ export interface Config { * The values are as follows: * * - 'require': Callers must provide Backstage user or service - * credentials with each request. The credentials are not - * forwarded to the proxy target. + * credentials with each request. The credentials are not forwarded + * to the proxy target. * - 'forward': Callers must provide Backstage user or service * credentials with each request, and those credentials are * forwarded to the proxy target. - * - 'dangerously-allow-unauthenticated': No Backstage credentials - * are required to access this proxy target. The target can still - * apply its own credentials checks, but the proxy will not help - * block non-Backstage-blessed callers. + * - 'dangerously-allow-unauthenticated': No Backstage credentials are + * required to access this proxy target. The target can still apply + * its own credentials checks, but the proxy will not help block + * non-Backstage-blessed callers. + * + * Note that if you have + * `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`, + * the `credentials` value does not apply; the proxy will behave as if + * all endpoints were set to `dangerously-allow-unauthenticated`. */ credentials?: | 'require' From b7cc1e8cc8547e00c2946fa03b8a9f1ffcc5c690 Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Tue, 7 May 2024 08:05:43 -0300 Subject: [PATCH 283/567] Update .changeset/nasty-papayas-heal.md Co-authored-by: Ben Lambert Signed-off-by: Matheus Castiglioni --- .changeset/nasty-papayas-heal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/nasty-papayas-heal.md b/.changeset/nasty-papayas-heal.md index cfeacb3238..371eba66a7 100644 --- a/.changeset/nasty-papayas-heal.md +++ b/.changeset/nasty-papayas-heal.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-github': minor --- -Adding support to change the default commit author for pull-request github action" +Adding support to change the default commit author for `publish:github:pull-request` From 13bd8ef5ba96ac194e4481a755288f41b08bbb86 Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Tue, 7 May 2024 08:05:56 -0300 Subject: [PATCH 284/567] Update .changeset/nasty-papayas-heal.md Co-authored-by: Ben Lambert Signed-off-by: Matheus Castiglioni --- .changeset/nasty-papayas-heal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/nasty-papayas-heal.md b/.changeset/nasty-papayas-heal.md index 371eba66a7..0e8e3f7ccc 100644 --- a/.changeset/nasty-papayas-heal.md +++ b/.changeset/nasty-papayas-heal.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend-module-github': minor +'@backstage/plugin-scaffolder-backend-module-github': patch --- Adding support to change the default commit author for `publish:github:pull-request` From 63ecf044b3d1d447095ed45d78130d5e3750688b Mon Sep 17 00:00:00 2001 From: Ruslan Gaiazov <44463016+freeyoungstrong@users.noreply.github.com> Date: Tue, 7 May 2024 13:10:28 +0200 Subject: [PATCH 285/567] Update analytics.md Updated links to analytics plugins that were moved to the new repository community-plugins Signed-off-by: Ruslan Gaiazov <44463016+freeyoungstrong@users.noreply.github.com> --- docs/plugins/analytics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 6b1f0a8254..52a859b5e9 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -46,9 +46,9 @@ To suggest an integration, please [open an issue][add-tool] for the analytics tool your organization uses. Or jump to [Writing Integrations][int-howto] to learn how to contribute the integration yourself! -[ga]: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga/README.md -[ga4]: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga4/README.md -[newrelic-browser]: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-newrelic-browser/README.md +[ga]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md +[ga4]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga4/README.md +[newrelic-browser]: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-newrelic-browser/README.md [qm]: https://github.com/quantummetric/analytics-module-qm/blob/main/README.md [matomo]: https://github.com/janus-idp/backstage-plugins/blob/main/plugins/analytics-module-matomo/README.md [add-tool]: https://github.com/backstage/backstage/issues/new?assignees=&labels=plugin&template=plugin_template.md&title=%5BAnalytics+Module%5D+THE+ANALYTICS+TOOL+TO+INTEGRATE From e40bd9a44000e712d858dd4e09da5ca8f2237b7a Mon Sep 17 00:00:00 2001 From: Stanislav C <150145013+stanislav-c@users.noreply.github.com> Date: Tue, 7 May 2024 14:08:41 +0200 Subject: [PATCH 286/567] fix: correct position of the copy to clipboard button Signed-off-by: Stanislav C <150145013+stanislav-c@users.noreply.github.com> --- .changeset/late-students-live.md | 5 +++++ plugins/techdocs/src/reader/transformers/copyToClipboard.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/late-students-live.md diff --git a/.changeset/late-students-live.md b/.changeset/late-students-live.md new file mode 100644 index 0000000000..6a7c203e9e --- /dev/null +++ b/.changeset/late-students-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fixed bug in CopyToClipboardButton component where positioning of the "Copy to clipboard" button in techdocs code snippets was broken in some cases diff --git a/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx b/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx index 70868baa21..53f407586a 100644 --- a/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx +++ b/plugins/techdocs/src/reader/transformers/copyToClipboard.tsx @@ -67,7 +67,7 @@ const CopyToClipboardButton = ({ text }: CopyToClipboardButtonProps) => { leaveDelay={1000} > From 178ffdd402409a308eb9222918ad75c25ec4b1fb Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 7 May 2024 14:25:39 +0200 Subject: [PATCH 287/567] techdocs-cli-embedded-app: default techdocs.builder to local Signed-off-by: Vincenzo Scamporlino --- packages/techdocs-cli-embedded-app/src/apis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/techdocs-cli-embedded-app/src/apis.ts b/packages/techdocs-cli-embedded-app/src/apis.ts index d48bee569d..5bb19a563d 100644 --- a/packages/techdocs-cli-embedded-app/src/apis.ts +++ b/packages/techdocs-cli-embedded-app/src/apis.ts @@ -72,7 +72,7 @@ class TechDocsDevStorageApi implements TechDocsStorageApi { } async getBuilder() { - return this.configApi.getString('techdocs.builder'); + return this.configApi.getOptionalString('techdocs.builder') || 'local'; } async getEntityDocs(_entityId: CompoundEntityRef, path: string) { From 8a18e9ef38bcfb390afebfb60c73fa764823e571 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 7 May 2024 14:28:02 +0200 Subject: [PATCH 288/567] techdocs: make builder optional Signed-off-by: Vincenzo Scamporlino --- plugins/techdocs-backend/config.d.ts | 2 +- .../techdocs-backend/src/service/DefaultDocsBuildStrategy.ts | 4 +++- plugins/techdocs/config.d.ts | 2 +- plugins/techdocs/src/client.ts | 2 +- plugins/techdocs/src/reader/components/TechDocsNotFound.tsx | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 01da1e7926..9d1cd9e170 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -24,7 +24,7 @@ export interface Config { * Documentation building process depends on the builder attr * @visibility frontend */ - builder: 'local' | 'external'; + builder?: 'local' | 'external'; /** * Techdocs generator information diff --git a/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.ts b/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.ts index bc2c586c82..a0bd623b64 100644 --- a/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.ts +++ b/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.ts @@ -29,6 +29,8 @@ export class DefaultDocsBuildStrategy implements DocsBuildStrategy { } async shouldBuild(_: { entity: Entity }): Promise { - return this.config.getString('techdocs.builder') === 'local'; + return [undefined, 'local'].includes( + this.config.getOptionalString('techdocs.builder'), + ); } } diff --git a/plugins/techdocs/config.d.ts b/plugins/techdocs/config.d.ts index 82ae8dd87a..0df852e490 100644 --- a/plugins/techdocs/config.d.ts +++ b/plugins/techdocs/config.d.ts @@ -24,7 +24,7 @@ export interface Config { * Documentation building process depends on the builder attr * @visibility frontend */ - builder: 'local' | 'external'; + builder?: 'local' | 'external'; /** * Allows fallback to case-sensitive triplets in case of migration issues. diff --git a/plugins/techdocs/src/client.ts b/plugins/techdocs/src/client.ts index ae81ecdaaa..0f11eb4068 100644 --- a/plugins/techdocs/src/client.ts +++ b/plugins/techdocs/src/client.ts @@ -151,7 +151,7 @@ export class TechDocsStorageClient implements TechDocsStorageApi { } async getBuilder(): Promise { - return this.configApi.getString('techdocs.builder'); + return this.configApi.getOptionalString('techdocs.builder') || 'local'; } /** diff --git a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx index bd2ef26b1e..858f9c5afc 100644 --- a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx @@ -39,7 +39,7 @@ export const TechDocsNotFound = ({ errorMessage }: Props) => { }, [analyticsApi, entityRef, location]); let additionalInfo = ''; - if (techdocsBuilder !== 'local') { + if (![undefined, 'local'].includes(techdocsBuilder)) { additionalInfo = "Note that techdocs.builder is not set to 'local' in your config, which means this Backstage app will not " + "generate docs if they are not found. Make sure the project's docs are generated and published by some external " + From 5863cf7137cef4f5e28a98b9af668fea8f934bb7 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 7 May 2024 14:32:17 +0200 Subject: [PATCH 289/567] techdocs: builder changesets Signed-off-by: Vincenzo Scamporlino --- .changeset/metal-years-rhyme.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/metal-years-rhyme.md diff --git a/.changeset/metal-years-rhyme.md b/.changeset/metal-years-rhyme.md new file mode 100644 index 0000000000..9c6daebdac --- /dev/null +++ b/.changeset/metal-years-rhyme.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-techdocs': patch +--- + +The `techdocs.builder` config is now optional and it will default to `local`. From b6d065fe3b742628c7e6c48a1595e8e8389728df Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 7 May 2024 14:50:50 +0200 Subject: [PATCH 290/567] chore: fixing changeset Signed-off-by: blam --- .changeset/sixty-bears-camp.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/sixty-bears-camp.md b/.changeset/sixty-bears-camp.md index ff6179c06f..f7a11482a7 100644 --- a/.changeset/sixty-bears-camp.md +++ b/.changeset/sixty-bears-camp.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder': minor +'@backstage/plugin-scaffolder': patch --- -`MultiEntityPicker` is able to be set as required +Fixed a bug where the `MultiEntityPicker` was not able to be set as required From fcee1ce391f6e21c86602ee24ff1da237a9a133d Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 7 May 2024 15:04:54 +0200 Subject: [PATCH 291/567] techdocs-backend: fix build strategy tests Signed-off-by: Vincenzo Scamporlino --- .../service/DefaultDocsBuildStrategy.test.ts | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.test.ts b/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.test.ts index 7868135b08..714a3736a9 100644 --- a/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.test.ts +++ b/plugins/techdocs-backend/src/service/DefaultDocsBuildStrategy.test.ts @@ -17,12 +17,6 @@ import { DefaultDocsBuildStrategy } from './DefaultDocsBuildStrategy'; import { ConfigReader } from '@backstage/config'; -const MockedConfigReader = ConfigReader as jest.MockedClass< - typeof ConfigReader ->; - -jest.mock('@backstage/config'); - describe('DefaultDocsBuildStrategy', () => { const entity = { apiVersion: 'backstage.io/v1alpha1', @@ -33,32 +27,40 @@ describe('DefaultDocsBuildStrategy', () => { }, }; - const config = new ConfigReader({}); - - beforeEach(() => { - jest.resetAllMocks(); - }); - describe('shouldBuild', () => { - it('should return true when techdocs.build is set to local', async () => { - const defaultDocsBuildStrategy = - DefaultDocsBuildStrategy.fromConfig(config); - - MockedConfigReader.prototype.getString.mockReturnValue('local'); + it('should return true when techdocs.builder is set to local', async () => { + const defaultDocsBuildStrategy = DefaultDocsBuildStrategy.fromConfig( + new ConfigReader({ + techdocs: { + builder: 'local', + }, + }), + ); const result = await defaultDocsBuildStrategy.shouldBuild({ entity }); expect(result).toBe(true); }); - it('should return false when techdocs.build is set to external', async () => { - const defaultDocsBuildStrategy = - DefaultDocsBuildStrategy.fromConfig(config); - - MockedConfigReader.prototype.getString.mockReturnValue('external'); + it('should return true when techdocs.builder is not set', async () => { + const defaultDocsBuildStrategy = DefaultDocsBuildStrategy.fromConfig( + new ConfigReader({ techdocs: {} }), + ); const result = await defaultDocsBuildStrategy.shouldBuild({ entity }); + expect(result).toBe(true); + }); + it('should return false when techdocs.builder is set to external', async () => { + const defaultDocsBuildStrategy = DefaultDocsBuildStrategy.fromConfig( + new ConfigReader({ + techdocs: { + builder: 'external', + }, + }), + ); + + const result = await defaultDocsBuildStrategy.shouldBuild({ entity }); expect(result).toBe(false); }); }); From d85dd88b3cea4c1ac49f08a389d6a9424e56d727 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 May 2024 13:27:02 +0000 Subject: [PATCH 292/567] Version Packages (next) --- .changeset/create-app-1715088359.md | 5 + .changeset/pre.json | 41 +- docs/releases/v1.27.0-next.2-changelog.md | 2411 +++++++++++++++++ package.json | 2 +- packages/app-next/CHANGELOG.md | 29 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 27 + packages/app/package.json | 2 +- packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 9 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 9 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- packages/backend-legacy/CHANGELOG.md | 26 + packages/backend-legacy/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 9 + packages/backend-test-utils/package.json | 2 +- packages/cli/CHANGELOG.md | 11 + packages/cli/package.json | 2 +- packages/core-components/CHANGELOG.md | 7 + packages/core-components/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 9 + packages/dev-utils/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 12 + packages/frontend-app-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 8 + packages/frontend-test-utils/package.json | 2 +- packages/integration-react/CHANGELOG.md | 7 + packages/integration-react/package.json | 2 +- packages/integration/CHANGELOG.md | 6 + packages/integration/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 11 + packages/repo-tools/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 11 + .../techdocs-cli-embedded-app/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 12 + plugins/api-docs/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 6 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 11 + plugins/auth-backend/package.json | 2 +- plugins/bitbucket-cloud-common/CHANGELOG.md | 7 + plugins/bitbucket-cloud-common/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 9 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 9 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 7 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 8 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 7 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 7 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 16 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 10 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 12 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 7 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 13 + plugins/catalog-react/package.json | 2 +- plugins/catalog/CHANGELOG.md | 17 + plugins/catalog/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 8 + plugins/devtools-backend/package.json | 2 +- plugins/events-node/CHANGELOG.md | 6 + plugins/events-node/package.json | 2 +- plugins/home/CHANGELOG.md | 10 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 8 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 8 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 8 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 9 + plugins/notifications-backend/package.json | 2 +- plugins/notifications/CHANGELOG.md | 8 + plugins/notifications/package.json | 2 +- plugins/org-react/CHANGELOG.md | 8 + plugins/org-react/package.json | 2 +- plugins/org/CHANGELOG.md | 11 + plugins/org/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 20 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 9 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 8 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 14 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/search-backend/CHANGELOG.md | 8 + plugins/search-backend/package.json | 2 +- plugins/search/CHANGELOG.md | 12 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 9 + plugins/signals-backend/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 11 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 11 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 8 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 14 + plugins/techdocs/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 10 + plugins/user-settings/package.json | 2 +- yarn.lock | 37 +- 174 files changed, 3437 insertions(+), 88 deletions(-) create mode 100644 .changeset/create-app-1715088359.md create mode 100644 docs/releases/v1.27.0-next.2-changelog.md create mode 100644 plugins/scaffolder-backend-module-notifications/CHANGELOG.md diff --git a/.changeset/create-app-1715088359.md b/.changeset/create-app-1715088359.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1715088359.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/pre.json b/.changeset/pre.json index f776d90b91..4a813e4e19 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -283,73 +283,112 @@ "@backstage/plugin-vault-node": "0.1.10", "@backstage/plugin-xcmetrics": "0.2.52", "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.0", - "@backstage/plugin-notifications-backend-module-email": "0.0.0" + "@backstage/plugin-notifications-backend-module-email": "0.0.0", + "example-backend-legacy": "0.2.98-next.1", + "@backstage/plugin-scaffolder-backend-module-notifications": "0.0.0" }, "changesets": [ "afraid-needles-divide", "blue-hotels-shake", + "brave-carrots-glow", "bright-pumpkins-rule", "chatty-cycles-unite", "chilly-adults-sing", "chilly-fireants-roll", "chilly-shoes-doubt", + "cold-cougars-float", "cold-rats-leave", "cool-elephants-march", "create-app-1714476054", + "create-app-1715088359", "cuddly-chairs-kick", "curly-shirts-flow", "curvy-planes-flash", + "cyan-eagles-hammer", "cyan-suns-shave", + "dirty-chairs-march", "dry-sloths-impress", + "early-starfishes-hammer", "eighty-apricots-kneel", "eighty-bats-stare", "eleven-pandas-divide", + "empty-beers-relax", "fix-stackoverflow", + "flat-countries-clap", "fluffy-hotels-wait", "four-cooks-serve", "fresh-crews-impress", "funny-bees-taste", "fuzzy-seahorses-tell", + "giant-donkeys-talk", "gold-waves-bake", "gorgeous-cameras-cross", "green-adults-push", "green-boxes-rescue", + "grumpy-toes-tap", + "happy-radios-kiss", + "healthy-dots-ring", + "healthy-shirts-roll", + "heavy-trainers-fly", "hip-carrots-drive", "hot-forks-train", "itchy-gorillas-hope", "itchy-keys-wonder", + "kind-toes-scream", "late-planes-fix", + "lazy-phones-worry", + "little-rockets-live", "loud-frogs-eat", "loud-timers-flow", "loud-vans-greet", "lovely-games-cry", "lucky-news-guess", "mean-ravens-dance", + "metal-years-rhyme", + "new-poets-promise", "orange-numbers-think", + "perfect-beers-explode", "perfect-points-hope", + "pink-years-peel", + "proud-comics-love", "proud-doors-cheat", "purple-parents-sin", "purple-waves-smile", + "quick-cats-argue", + "quiet-boxes-build", "rare-fireants-tickle", "real-crabs-obey", "renovate-0d0bd5c", + "rich-adults-float", "selfish-pigs-glow", + "selfish-walls-visit", + "sharp-glasses-live", "shy-students-clap", "silent-wombats-hang", "six-scissors-smile", + "sixty-bears-camp", + "slimy-kids-behave", "smart-avocados-invent", "smooth-garlics-behave", "sour-socks-approve", "stupid-onions-know", "sweet-zoos-clap", + "swift-humans-hunt", "tall-ads-shave", + "tame-jars-double", "tasty-apes-learn", + "tasty-moles-jog", + "tasty-rats-explain", "thick-llamas-itch", "thick-terms-rush", + "thirty-mangos-travel", + "tough-eggs-wink", "tricky-cougars-shout", "unlucky-days-play", "unlucky-rivers-collect", "warm-fans-promise", + "wet-files-pretend", + "wild-seahorses-grin", "young-guests-reflect", "young-olives-drop" ] diff --git a/docs/releases/v1.27.0-next.2-changelog.md b/docs/releases/v1.27.0-next.2-changelog.md new file mode 100644 index 0000000000..ac8fb0534f --- /dev/null +++ b/docs/releases/v1.27.0-next.2-changelog.md @@ -0,0 +1,2411 @@ +# Release v1.27.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.27.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.27.0-next.2) + +## @backstage/backend-app-api@0.7.3-next.1 + +# @backstage/backend-app-api + +## 0.7.2-next.1 + +### Patch Changes + +- 09f8988: Remove explicit `alg` check for user tokens in `verifyToken` +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## 0.7.1-next.0 + +### Patch Changes + +- 4cd5ff0: Add ability to configure the Node.js HTTP Server when configuring the root HTTP Router service +- e8199b1: Move the JWKS registration outside of the lifecycle middleware +- dc8c5dd: The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests. +- 025641b: Redact `meta` fields too with the logger +- 5863e02: Internal refactor to only create one external token handler +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## 0.7.0 + +### Minor Changes + +- 3256f14: **BREAKING**: Modules are no longer loaded unless the plugin that they extend is present. + +### Patch Changes + +- 10327fb: Deprecate the `getPath` option for the `httpRouterServiceFactory` and more generally the ability to configure plugin API paths to be anything else than `/api/:pluginId/`. Requests towards `/api/*` that do not match an installed plugin will also no longer be handled by the index router, typically instead returning a 404. + +- 2c50516: Fix auth cookie issuance for split backend deployments by preferring to set it against the request target host instead of origin + +- 7e584d6: Fixed a bug where expired cookies would not be refreshed. + +- 1a20b12: Make the auth service create and validate dedicated OBO tokens, containing the user identity proof. + +- 00fca28: Implemented support for external access using both the legacy token form and static tokens. + +- d5a1fe1: Replaced winston logger with `LoggerService` + +- bce0879: Service-to-service authentication has been improved. + + Each plugin now has the capability to generate its own signing keys for token issuance. The generated public keys are stored in a database, and they are made accessible through a newly created endpoint: `/.backstage/auth/v1/jwks.json`. + + `AuthService` can now issue tokens with a reduced scope using the `getPluginRequestToken` method. This improvement enables plugins to identify the plugin originating the request. + +- 54f2ac8: Added `initialization` option to `createServiceFactory` which defines the initialization strategy for the service. The default strategy mimics the current behavior where plugin scoped services are initialized lazily by default and root scoped services are initialized eagerly. + +- 56f81b5: Improved error message thrown by `AuthService` when requesting a token for plugins that don't support the new authentication tokens. + +- 25ea3d2: Minor internal restructuring + +- d62bc51: Add support for limited user tokens by using user identity proof provided by the auth backend. + +- c884b9a: Automatically creates a get and delete cookie endpoint when a `user-cookie` policy is added. + +- Updated dependencies + - @backstage/backend-common@0.21.7 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-permission-node@0.7.28 + - @backstage/backend-plugin-api@0.6.17 + - @backstage/backend-tasks@0.5.22 + - @backstage/plugin-auth-node@0.4.12 + - @backstage/cli-node@0.2.5 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.0-next.1 + +### Minor Changes + +- 3256f14: **BREAKING**: Modules are no longer loaded unless the plugin that they extend is present. + +### Patch Changes + +- 10327fb: Deprecate the `getPath` option for the `httpRouterServiceFactory` and more generally the ability to configure plugin API paths to be anything else than `/api/:pluginId/`. Requests towards `/api/*` that do not match an installed plugin will also no longer be handled by the index router, typically instead returning a 404. + +- 1a20b12: Make the auth service create and validate dedicated OBO tokens, containing the user identity proof. + +- bce0879: Service-to-service authentication has been improved. + + Each plugin now has the capability to generate its own signing keys for token issuance. The generated public keys are stored in a database, and they are made accessible through a newly created endpoint: `/.backstage/auth/v1/jwks.json`. + + `AuthService` can now issue tokens with a reduced scope using the `getPluginRequestToken` method. This improvement enables plugins to identify the plugin originating the request. + +- 54f2ac8: Added `initialization` option to `createServiceFactory` which defines the initialization strategy for the service. The default strategy mimics the current behavior where plugin scoped services are initialized lazily by default and root scoped services are initialized eagerly. + +- d62bc51: Add support for limited user tokens by using user identity proof provided by the auth backend. + +- c884b9a: Automatically creates a get and delete cookie endpoint when a `user-cookie` policy is added. + +- Updated dependencies + - @backstage/backend-common@0.21.7-next.1 + - @backstage/backend-plugin-api@0.6.17-next.1 + - @backstage/plugin-auth-node@0.4.12-next.1 + - @backstage/backend-tasks@0.5.22-next.1 + - @backstage/plugin-permission-node@0.7.28-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.4 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.6.3-next.0 + +### Patch Changes + +- 7e584d6: Fixed a bug where expired cookies would not be refreshed. +- Updated dependencies + - @backstage/backend-common@0.21.7-next.0 + - @backstage/config-loader@1.8.0-next.0 + - @backstage/backend-plugin-api@0.6.17-next.0 + - @backstage/backend-tasks@0.5.22-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.4 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.12-next.0 + - @backstage/plugin-permission-node@0.7.28-next.0 + +## 0.6.2 + +### Patch Changes + +- e848644: Temporarily revert the rate limiting +- Updated dependencies + - @backstage/plugin-auth-node@0.4.11 + - @backstage/backend-common@0.21.6 + - @backstage/backend-plugin-api@0.6.16 + - @backstage/plugin-permission-node@0.7.27 + - @backstage/backend-tasks@0.5.21 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.4 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.7.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.6.1 + +### Patch Changes + +- de1f45d: Temporarily revert the rate limiting +- Updated dependencies + - @backstage/backend-common@0.21.5 + - @backstage/plugin-auth-node@0.4.10 + - @backstage/backend-tasks@0.5.20 + - @backstage/plugin-permission-node@0.7.26 + - @backstage/backend-plugin-api@0.6.15 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.4 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.7.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.6.0 + +### Minor Changes + +- 4a3d434: **BREAKING**: For users that have migrated to the new backend system, incoming requests will now be rejected if they are not properly authenticated (e.g. with a Backstage bearer token or a backend token). Please see the [Auth Service Migration tutorial](https://backstage.io/docs/tutorials/auth-service-migration) for more information on how to circumvent this behavior in the short term and how to properly leverage it in the longer term. + + Added service factories for the new [`auth`](https://backstage.io/docs/backend-system/core-services/auth/), [`httpAuth`](https://backstage.io/docs/backend-system/core-services/http-auth), and [`userInfo`](https://backstage.io/docs/backend-system/core-services/user-info) services that were created as part of [BEP-0003](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution). + +### Patch Changes + +- 999224f: Bump dependency `minimatch` to v9 +- 81e0120: Fixed an issue where configuration schema for the purpose of redacting secrets from logs was not being read correctly. +- 15fda44: Provide some sane defaults for `WinstonLogger.create` making some of the arguments optional +- 0502d82: Updated the `permissionsServiceFactory` to forward the `AuthService` to the implementation. +- 9d91128: Add the possibility to disable watching files in the new backend system +- a5d341e: Adds an initial rate-limiting implementation so that any incoming requests that have a `'none'` principal are rate-limited automatically. +- 9802004: Made the `DefaultUserInfoService` claims check stricter +- f235ca7: Make sure to not filter out schemas in `createConfigSecretEnumerator` +- af5f7a6: The experimental feature discovery service exported at the `/alpha` sub-path will no longer attempt to load packages that are not Backstage backend packages. +- Updated dependencies + - @backstage/backend-common@0.21.4 + - @backstage/plugin-auth-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/backend-plugin-api@0.6.14 + - @backstage/config-loader@1.7.0 + - @backstage/backend-tasks@0.5.19 + - @backstage/plugin-permission-node@0.7.25 + - @backstage/cli-node@0.2.4 + - @backstage/cli-common@0.1.13 + - @backstage/types@1.1.1 + +## 0.6.0-next.2 + +### Patch Changes + +- 15fda44: Provide some sane defaults for `WinstonLogger.create` making some of the arguments optional +- 9d91128: Add the possibility to disable watching files in the new backend system +- Updated dependencies + - @backstage/backend-common@0.21.4-next.2 + - @backstage/plugin-auth-node@0.4.9-next.2 + - @backstage/backend-plugin-api@0.6.14-next.2 + - @backstage/backend-tasks@0.5.19-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.4-next.0 + - @backstage/config@1.2.0-next.1 + - @backstage/config-loader@1.7.0-next.1 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.25-next.2 + +## 0.6.0-next.1 + +### Patch Changes + +- 81e0120: Fixed an issue where configuration schema for the purpose of redacting secrets from logs was not being read correctly. +- f235ca7: Make sure to not filter out schemas in `createConfigSecretEnumerator` +- Updated dependencies + - @backstage/config@1.2.0-next.1 + - @backstage/config-loader@1.7.0-next.1 + - @backstage/backend-common@0.21.4-next.1 + - @backstage/backend-plugin-api@0.6.14-next.1 + - @backstage/backend-tasks@0.5.19-next.1 + - @backstage/plugin-auth-node@0.4.9-next.1 + - @backstage/plugin-permission-node@0.7.25-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.4-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/types@1.1.1 + +## 0.6.0-next.0 + +### Minor Changes + +- 4a3d434: **BREAKING**: For users that have migrated to the new backend system, incoming requests will now be rejected if they are not properly authenticated (e.g. with a Backstage bearer token or a backend token). Please see the [Auth Service Migration tutorial](https://backstage.io/docs/tutorials/auth-service-migration) for more information on how to circumvent this behavior in the short term and how to properly leverage it in the longer term. + + Added service factories for the new [`auth`](https://backstage.io/docs/backend-system/core-services/auth/), [`httpAuth`](https://backstage.io/docs/backend-system/core-services/http-auth), and [`userInfo`](https://backstage.io/docs/backend-system/core-services/user-info) services that were created as part of [BEP-0003](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution). + +### Patch Changes + +- 999224f: Bump dependency `minimatch` to v9 +- 0502d82: Updated the `permissionsServiceFactory` to forward the `AuthService` to the implementation. +- 9802004: Made the `DefaultUserInfoService` claims check stricter +- Updated dependencies + - @backstage/backend-common@0.21.3-next.0 + - @backstage/plugin-auth-node@0.4.8-next.0 + - @backstage/errors@1.2.4-next.0 + - @backstage/backend-plugin-api@0.6.13-next.0 + - @backstage/backend-tasks@0.5.18-next.0 + - @backstage/plugin-permission-node@0.7.24-next.0 + - @backstage/cli-node@0.2.4-next.0 + - @backstage/config-loader@1.6.3-next.0 + - @backstage/config@1.1.2-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/types@1.1.1 + +## 0.5.11 + +### Patch Changes + +- e0c18ef: Include the extension point ID and the module ID in the backend init error message. +- 7ae5704: Updated the default error handling middleware to filter out certain known error types that should never be returned in responses. The errors are instead logged along with a correlation ID, which is also returned in the response. Initially only PostgreSQL protocol errors from the `pg-protocol` package are filtered out. +- 9aac2b0: Use `--cwd` as the first `yarn` argument +- 54ad8e1: Allow the `createConfigSecretEnumerator` to take an optional `schema` argument with an already-loaded global configuration schema. +- 6bb6f3e: Updated dependency `fs-extra` to `^11.2.0`. + Updated dependency `@types/fs-extra` to `^11.0.0`. +- Updated dependencies + - @backstage/backend-common@0.21.0 + - @backstage/plugin-auth-node@0.4.4 + - @backstage/cli-node@0.2.3 + - @backstage/backend-plugin-api@0.6.10 + - @backstage/backend-tasks@0.5.15 + - @backstage/config-loader@1.6.2 + - @backstage/plugin-permission-node@0.7.21 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.11-next.3 + +### Patch Changes + +- 54ad8e1: Allow the `createConfigSecretEnumerator` to take an optional `schema` argument with an already-loaded global configuration schema. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.3 + - @backstage/cli-node@0.2.3-next.0 + - @backstage/backend-tasks@0.5.15-next.3 + - @backstage/config-loader@1.6.2-next.0 + - @backstage/plugin-auth-node@0.4.4-next.3 + - @backstage/plugin-permission-node@0.7.21-next.3 + - @backstage/backend-plugin-api@0.6.10-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.11-next.2 + +### Patch Changes + +- 9aac2b0: Use `--cwd` as the first `yarn` argument +- Updated dependencies + - @backstage/backend-common@0.21.0-next.2 + - @backstage/backend-plugin-api@0.6.10-next.2 + - @backstage/backend-tasks@0.5.15-next.2 + - @backstage/plugin-auth-node@0.4.4-next.2 + - @backstage/plugin-permission-node@0.7.21-next.2 + - @backstage/config@1.1.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.2 + - @backstage/config-loader@1.6.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.1 + - @backstage/backend-common@0.21.0-next.1 + - @backstage/backend-tasks@0.5.15-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.2 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.4-next.1 + - @backstage/plugin-permission-node@0.7.21-next.1 + +## 0.5.11-next.0 + +### Patch Changes + +- e0c18ef: Include the extension point ID and the module ID in the backend init error message. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/config-loader@1.6.1 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.10 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config-loader@1.6.1 + - @backstage/cli-node@0.2.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.10-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/config-loader@1.6.1-next.0 + +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.5.9 + +### Patch Changes + +- 1da5f43: Ensure redaction of secrets that have accidental extra whitespace around them +- 9f8f266: Add redacting for secrets in stack traces of logs +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config-loader@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/cli-node@0.2.1 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.5.9-next.1 + +### Patch Changes + +- 1da5f434f3: Ensure redaction of secrets that have accidental extra whitespace around them +- 9f8f266ff4: Add redacting for secrets in stack traces of logs +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## 0.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.0 + - @backstage/backend-tasks@0.5.13-next.0 + - @backstage/plugin-auth-node@0.4.2-next.0 + - @backstage/plugin-permission-node@0.7.19-next.0 + - @backstage/backend-plugin-api@0.6.8-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.8 + +### Patch Changes + +- bc9a18d5ec: Added a workaround for double `default` wrapping when dynamically importing CommonJS modules with default exports. +- Updated dependencies + - @backstage/config-loader@1.5.3 + - @backstage/cli-node@0.2.0 + - @backstage/backend-common@0.19.9 + - @backstage/backend-plugin-api@0.6.7 + - @backstage/backend-tasks@0.5.12 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1 + - @backstage/plugin-permission-node@0.7.18 + +## 0.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.7-next.2 + - @backstage/backend-common@0.19.9-next.2 + - @backstage/backend-tasks@0.5.12-next.2 + - @backstage/plugin-auth-node@0.4.1-next.2 + - @backstage/plugin-permission-node@0.7.18-next.2 + - @backstage/config-loader@1.5.3-next.0 + +## 0.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.9-next.1 + - @backstage/backend-tasks@0.5.12-next.1 + - @backstage/config-loader@1.5.3-next.0 + - @backstage/plugin-auth-node@0.4.1-next.1 + - @backstage/plugin-permission-node@0.7.18-next.1 + - @backstage/backend-plugin-api@0.6.7-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.8-next.0 + +### Patch Changes + +- bc9a18d5ec: Added a workaround for double `default` wrapping when dynamically importing CommonJS modules with default exports. +- Updated dependencies + - @backstage/config-loader@1.5.2-next.0 + - @backstage/cli-node@0.2.0-next.0 + - @backstage/backend-common@0.19.9-next.0 + - @backstage/backend-plugin-api@0.6.7-next.0 + - @backstage/backend-tasks@0.5.12-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.1-next.0 + - @backstage/plugin-permission-node@0.7.18-next.0 + +## 0.5.6 + +### Patch Changes + +- 74491c9602: Moved `HostDiscovery` from `@backstage/backend-common`. +- a4617c422a: Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`. +- Updated dependencies + - @backstage/backend-tasks@0.5.11 + - @backstage/backend-common@0.19.8 + - @backstage/plugin-auth-node@0.4.0 + - @backstage/config-loader@1.5.1 + - @backstage/errors@1.2.3 + - @backstage/cli-common@0.1.13 + - @backstage/backend-plugin-api@0.6.6 + - @backstage/plugin-permission-node@0.7.17 + - @backstage/cli-node@0.1.5 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.5.6-next.2 + +### Patch Changes + +- 74491c9602: Moved `HostDiscovery` from `@backstage/backend-common`. +- a4617c422a: Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`. +- Updated dependencies + - @backstage/backend-common@0.19.8-next.2 + - @backstage/plugin-auth-node@0.4.0-next.2 + - @backstage/config-loader@1.5.1-next.1 + - @backstage/errors@1.2.3-next.0 + - @backstage/backend-tasks@0.5.11-next.2 + - @backstage/plugin-permission-node@0.7.17-next.2 + - @backstage/backend-plugin-api@0.6.6-next.2 + - @backstage/cli-common@0.1.13-next.0 + - @backstage/cli-node@0.1.5-next.1 + - @backstage/config@1.1.1-next.0 + - @backstage/types@1.1.1 + +## 0.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.10-next.1 + - @backstage/backend-common@0.19.7-next.1 + - @backstage/backend-plugin-api@0.6.5-next.1 + - @backstage/plugin-auth-node@0.3.2-next.1 + - @backstage/plugin-permission-node@0.7.16-next.1 + - @backstage/config@1.1.0 + - @backstage/cli-common@0.1.13-next.0 + - @backstage/cli-node@0.1.5-next.0 + - @backstage/config-loader@1.5.1-next.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.2-next.0 + - @backstage/config-loader@1.5.1-next.0 + - @backstage/cli-common@0.1.13-next.0 + - @backstage/backend-common@0.19.7-next.0 + - @backstage/config@1.1.0 + - @backstage/backend-plugin-api@0.6.5-next.0 + - @backstage/backend-tasks@0.5.10-next.0 + - @backstage/cli-node@0.1.5-next.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.16-next.0 + +## 0.5.3 + +### Patch Changes + +- 154632d8753b: Add support for discovering additional service factories during startup. +- 37a20c7f14aa: Adds include and exclude configuration to feature discovery of backend packages + Adds alpha modules to feature discovery +- cb7fc410ed99: The experimental backend feature discovery now only considers default exports from packages. It no longer filters packages to include based on the package role, except that `'cli'` packages are ignored. However, the `"backstage"` field is still required in `package.json`. +- 3fc64b9e2f8f: Extension points are now tracked via their ID rather than reference, in order to support package duplication. +- 3b30b179cb38: Add support for installing features as package imports, for example `backend.add(import('my-plugin'))`. +- b219d097b3f4: Backend startup will now fail if any circular service dependencies are detected. +- Updated dependencies + - @backstage/backend-tasks@0.5.8 + - @backstage/backend-common@0.19.5 + - @backstage/plugin-auth-node@0.3.0 + - @backstage/config@1.1.0 + - @backstage/errors@1.2.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.14 + - @backstage/backend-plugin-api@0.6.3 + - @backstage/config-loader@1.5.0 + - @backstage/cli-common@0.1.12 + - @backstage/cli-node@0.1.4 + +## 0.5.3-next.3 + +### Patch Changes + +- 154632d8753b: Add support for discovering additional service factories during startup. +- cb7fc410ed99: The experimental backend feature discovery now only considers default exports from packages. It no longer filters packages to include based on the package role, except that `'cli'` packages are ignored. However, the `"backstage"` field is still required in `package.json`. +- 3b30b179cb38: Add support for installing features as package imports, for example `backend.add(import('my-plugin'))`. +- Updated dependencies + - @backstage/config@1.1.0-next.2 + - @backstage/errors@1.2.2-next.0 + - @backstage/types@1.1.1-next.0 + - @backstage/plugin-permission-node@0.7.14-next.3 + - @backstage/backend-plugin-api@0.6.3-next.3 + - @backstage/backend-common@0.19.5-next.3 + - @backstage/backend-tasks@0.5.8-next.3 + - @backstage/cli-common@0.1.12 + - @backstage/cli-node@0.1.4-next.0 + - @backstage/config-loader@1.5.0-next.3 + - @backstage/plugin-auth-node@0.3.0-next.3 + +## 0.5.3-next.2 + +### Patch Changes + +- 37a20c7f14aa: Adds include and exclude configuration to feature discovery of backend packages + Adds alpha modules to feature discovery +- 3fc64b9e2f8f: Extension points are now tracked via their ID rather than reference, in order to support package duplication. +- b219d097b3f4: Backend startup will now fail if any circular service dependencies are detected. +- Updated dependencies + - @backstage/config-loader@1.5.0-next.2 + - @backstage/config@1.1.0-next.1 + - @backstage/backend-tasks@0.5.8-next.2 + - @backstage/backend-common@0.19.5-next.2 + - @backstage/plugin-auth-node@0.3.0-next.2 + - @backstage/plugin-permission-node@0.7.14-next.2 + - @backstage/backend-plugin-api@0.6.3-next.2 + - @backstage/cli-common@0.1.12 + - @backstage/cli-node@0.1.3 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.0-next.0 + - @backstage/backend-tasks@0.5.8-next.1 + - @backstage/backend-common@0.19.5-next.1 + - @backstage/backend-plugin-api@0.6.3-next.1 + - @backstage/config-loader@1.5.0-next.1 + - @backstage/plugin-auth-node@0.3.0-next.1 + - @backstage/plugin-permission-node@0.7.14-next.1 + - @backstage/cli-common@0.1.12 + - @backstage/cli-node@0.1.3 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.3.0-next.0 + - @backstage/backend-common@0.19.4-next.0 + - @backstage/config-loader@1.5.0-next.0 + - @backstage/backend-tasks@0.5.7-next.0 + - @backstage/backend-plugin-api@0.6.2-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/cli-node@0.1.3 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-permission-node@0.7.13-next.0 + +## 0.5.0 + +### Minor Changes + +- b9c57a4f857e: **BREAKING**: Renamed `configServiceFactory` to `rootConfigServiceFactory`. +- a6d7983f349c: **BREAKING**: Removed the `services` option from `createBackend`. Service factories are now `BackendFeature`s and should be installed with `backend.add(...)` instead. The following should be migrated: + + ```ts + const backend = createBackend({ services: [myCustomServiceFactory] }); + ``` + + To instead pass the service factory via `backend.add(...)`: + + ```ts + const backend = createBackend(); + backend.add(customRootLoggerServiceFactory); + ``` + +### Patch Changes + +- e65c4896f755: Do not throw in backend.stop, if start failed +- c7aa4ff1793c: Allow modules to register extension points. +- 57a10c6c69cc: Add validation to make sure that extension points do not cross plugin boundaries. +- cc9256a33bcc: Added new experimental `featureDiscoveryServiceFactory`, available as an `/alpha` export. +- Updated dependencies + - @backstage/backend-common@0.19.2 + - @backstage/config-loader@1.4.0 + - @backstage/backend-plugin-api@0.6.0 + - @backstage/cli-node@0.1.3 + - @backstage/plugin-auth-node@0.2.17 + - @backstage/backend-tasks@0.5.5 + - @backstage/plugin-permission-node@0.7.11 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.5.0-next.2 + +### Patch Changes + +- e65c4896f755: Do not throw in backend.stop, if start failed +- cc9256a33bcc: Added new experimental `featureDiscoveryServiceFactory`, available as an `/alpha` export. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.0-next.2 + - @backstage/backend-tasks@0.5.5-next.2 + - @backstage/backend-common@0.19.2-next.2 + - @backstage/plugin-permission-node@0.7.11-next.2 + - @backstage/plugin-auth-node@0.2.17-next.2 + - @backstage/config-loader@1.4.0-next.1 + +## 0.5.0-next.1 + +### Minor Changes + +- b9c57a4f857e: **BREAKING**: Renamed `configServiceFactory` to `rootConfigServiceFactory`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.2-next.1 + - @backstage/config-loader@1.4.0-next.1 + - @backstage/plugin-auth-node@0.2.17-next.1 + - @backstage/backend-plugin-api@0.6.0-next.1 + - @backstage/backend-tasks@0.5.5-next.1 + - @backstage/plugin-permission-node@0.7.11-next.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.4.0-next.0 + - @backstage/backend-common@0.19.2-next.0 + - @backstage/backend-plugin-api@0.5.5-next.0 + - @backstage/backend-tasks@0.5.5-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/errors@1.2.1 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.17-next.0 + - @backstage/plugin-permission-node@0.7.11-next.0 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1 + - @backstage/backend-common@0.19.1 + - @backstage/backend-plugin-api@0.5.4 + - @backstage/backend-tasks@0.5.4 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/config-loader@1.3.2 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.16 + - @backstage/plugin-permission-node@0.7.10 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/backend-common@0.19.1-next.0 + - @backstage/backend-plugin-api@0.5.4-next.0 + - @backstage/backend-tasks@0.5.4-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + - @backstage/config-loader@1.3.2-next.0 + - @backstage/types@1.1.0 + - @backstage/plugin-auth-node@0.2.16-next.0 + - @backstage/plugin-permission-node@0.7.10-next.0 + +## 0.4.4 + +### Patch Changes + +- 3bb4158a8aa4: Switched startup strategy to initialize all plugins in parallel, as well as hook into the new startup lifecycle hooks. +- 68a21956ef52: Remove reference to deprecated import +- a5c5491ff50c: Use `durationToMilliseconds` from `@backstage/types` instead of our own +- 2c9f67e6f166: Introduced built-in middleware into the default `HttpService` implementation that throws a `ServiceNotAvailable` error when plugins aren't able to serve request. Also introduced a request stalling mechanism that pauses incoming request until plugins have been fully initialized. +- c4e8fefd9f13: Added handling of `ServiceUnavailableError` to error handling middleware. +- Updated dependencies + - @backstage/backend-common@0.19.0 + - @backstage/types@1.1.0 + - @backstage/config-loader@1.3.1 + - @backstage/errors@1.2.0 + - @backstage/backend-plugin-api@0.5.3 + - @backstage/backend-tasks@0.5.3 + - @backstage/plugin-auth-node@0.2.15 + - @backstage/plugin-permission-node@0.7.9 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.8 + +## 0.4.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.19.0-next.2 + - @backstage/backend-plugin-api@0.5.3-next.2 + - @backstage/backend-tasks@0.5.3-next.2 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/config-loader@1.3.1-next.1 + - @backstage/errors@1.2.0-next.0 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.15-next.2 + - @backstage/plugin-permission-node@0.7.9-next.2 + +## 0.4.4-next.1 + +### Patch Changes + +- 3bb4158a8aa4: Switched startup strategy to initialize all plugins in parallel, as well as hook into the new startup lifecycle hooks. +- 2c9f67e6f166: Introduced built-in middleware into the default `HttpService` implementation that throws a `ServiceNotAvailable` error when plugins aren't able to serve request. Also introduced a request stalling mechanism that pauses incoming request until plugins have been fully initialized. +- c4e8fefd9f13: Added handling of `ServiceUnavailableError` to error handling middleware. +- Updated dependencies + - @backstage/backend-common@0.19.0-next.1 + - @backstage/errors@1.2.0-next.0 + - @backstage/backend-plugin-api@0.5.3-next.1 + - @backstage/backend-tasks@0.5.3-next.1 + - @backstage/plugin-auth-node@0.2.15-next.1 + - @backstage/plugin-permission-node@0.7.9-next.1 + - @backstage/config-loader@1.3.1-next.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.3.1-next.0 + - @backstage/backend-common@0.18.6-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-plugin-api@0.5.3-next.0 + - @backstage/backend-tasks@0.5.3-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.15-next.0 + - @backstage/plugin-permission-node@0.7.9-next.0 + +## 0.4.3 + +### Patch Changes + +- cf13b482f9e: Switch `configServiceFactory` to use `ConfigSources` from `@backstage/config-loader` to load config. +- Updated dependencies + - @backstage/backend-common@0.18.5 + - @backstage/config-loader@1.3.0 + - @backstage/plugin-permission-node@0.7.8 + - @backstage/backend-tasks@0.5.2 + - @backstage/plugin-auth-node@0.2.14 + - @backstage/backend-plugin-api@0.5.2 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.5-next.1 + - @backstage/backend-tasks@0.5.2-next.1 + - @backstage/plugin-auth-node@0.2.14-next.1 + - @backstage/plugin-permission-node@0.7.8-next.1 + - @backstage/backend-plugin-api@0.5.2-next.1 + - @backstage/config-loader@1.3.0-next.0 + - @backstage/config@1.0.7 + +## 0.4.3-next.0 + +### Patch Changes + +- cf13b482f9e: Switch `configServiceFactory` to use `ConfigSources` from `@backstage/config-loader` to load config. +- Updated dependencies + - @backstage/backend-common@0.18.5-next.0 + - @backstage/config-loader@1.3.0-next.0 + - @backstage/plugin-permission-node@0.7.8-next.0 + - @backstage/backend-tasks@0.5.2-next.0 + - @backstage/plugin-auth-node@0.2.14-next.0 + - @backstage/backend-plugin-api@0.5.2-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 0.4.2 + +### Patch Changes + +- 5c7ce585824: Allow an additionalConfig to be provided to loadBackendConfig that fetches config values during runtime. +- 8cce2205a39: Register unhandled rejection and uncaught exception handlers to avoid backend crashes. +- Updated dependencies + - @backstage/backend-common@0.18.4 + - @backstage/config-loader@1.2.0 + - @backstage/plugin-permission-node@0.7.7 + - @backstage/backend-tasks@0.5.1 + - @backstage/plugin-auth-node@0.2.13 + - @backstage/backend-plugin-api@0.5.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 0.4.2-next.2 + +### Patch Changes + +- 5c7ce585824: Allow an additionalConfig to be provided to loadBackendConfig that fetches config values during runtime. +- Updated dependencies + - @backstage/backend-common@0.18.4-next.2 + - @backstage/plugin-permission-node@0.7.7-next.2 + - @backstage/backend-plugin-api@0.5.1-next.2 + - @backstage/backend-tasks@0.5.1-next.2 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/config-loader@1.1.9 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.2 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.7.7-next.1 + - @backstage/backend-tasks@0.5.1-next.1 + - @backstage/backend-common@0.18.4-next.1 + - @backstage/backend-plugin-api@0.5.1-next.1 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/config-loader@1.1.9 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.1 + +## 0.4.2-next.0 + +### Patch Changes + +- 8cce2205a39: Register unhandled rejection and uncaught exception handlers to avoid backend crashes. +- Updated dependencies + - @backstage/backend-common@0.18.4-next.0 + - @backstage/config@1.0.7 + - @backstage/backend-plugin-api@0.5.1-next.0 + - @backstage/backend-tasks@0.5.1-next.0 + - @backstage/cli-common@0.1.12 + - @backstage/config-loader@1.1.9 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.13-next.0 + - @backstage/plugin-permission-node@0.7.7-next.0 + +## 0.4.1 + +### Patch Changes + +- 928a12a9b3e: Internal refactor of `/alpha` exports. +- 482dae5de1c: Updated link to docs. +- 915e46622cf: Add support for `NotImplementedError`, properly returning 501 as status code. +- Updated dependencies + - @backstage/plugin-permission-node@0.7.6 + - @backstage/plugin-auth-node@0.2.12 + - @backstage/backend-tasks@0.5.0 + - @backstage/backend-common@0.18.3 + - @backstage/errors@1.1.5 + - @backstage/backend-plugin-api@0.5.0 + - @backstage/config-loader@1.1.9 + - @backstage/cli-common@0.1.12 + - @backstage/config@1.0.7 + - @backstage/types@1.0.2 + +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.2.12-next.2 + - @backstage/backend-tasks@0.5.0-next.2 + - @backstage/backend-common@0.18.3-next.2 + - @backstage/backend-plugin-api@0.4.1-next.2 + - @backstage/plugin-permission-node@0.7.6-next.2 + - @backstage/config@1.0.7-next.0 + +## 0.4.1-next.1 + +### Patch Changes + +- 482dae5de1c: Updated link to docs. +- 915e46622cf: Add support for `NotImplementedError`, properly returning 501 as status code. +- Updated dependencies + - @backstage/plugin-permission-node@0.7.6-next.1 + - @backstage/errors@1.1.5-next.0 + - @backstage/backend-common@0.18.3-next.1 + - @backstage/config-loader@1.1.9-next.0 + - @backstage/plugin-auth-node@0.2.12-next.1 + - @backstage/backend-plugin-api@0.4.1-next.1 + - @backstage/backend-tasks@0.4.4-next.1 + - @backstage/cli-common@0.1.12-next.0 + - @backstage/config@1.0.7-next.0 + - @backstage/types@1.0.2 + +## 0.4.1-next.0 + +### Patch Changes + +- 928a12a9b3: Internal refactor of `/alpha` exports. +- Updated dependencies + - @backstage/backend-tasks@0.4.4-next.0 + - @backstage/backend-plugin-api@0.4.1-next.0 + - @backstage/backend-common@0.18.3-next.0 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + - @backstage/config-loader@1.1.8 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.12-next.0 + - @backstage/plugin-permission-node@0.7.6-next.0 + +## 0.4.0 + +### Minor Changes + +- 01a075ec1d: **BREAKING**: Renamed `RootHttpRouterConfigureOptions` to `RootHttpRouterConfigureContext`, and removed the unused type `ServiceOrExtensionPoint`. +- 4ae71b7f2e: **BREAKING** Renaming `*Factory` exports to `*ServiceFactory` instead. For example `configFactory` now is exported as `configServiceFactory`. +- d31d8e00b3: **BREAKING** `HttpServerCertificateOptions` when specified with a `key` and `cert` should also have the `type: 'pem'` instead of `type: 'plain'` + +### Patch Changes + +- a18da2f8b5: Fixed an issue were the log redaction didn't properly escape RegExp characters. +- 5febb216fe: Updated to match the new `CacheService` interface. +- e716946103: Updated usage of the lifecycle service. +- f60cca9da1: Updated database factory to pass service deps required for restoring database state during development. +- 610d65e143: Updates to match new `BackendFeature` type. +- 725383f69d: Tweaked messaging in the README. +- b86efa2d04: Updated usage of `ServiceFactory`. +- ab22515647: The shutdown signal handlers are now installed as part of the backend instance rather than the lifecycle service, and explicitly cause the process to exit. +- b729f9f31f: Moved the options of the `config` and `rootHttpRouter` services out to the factories themselves, where they belong +- ed8b5967d7: `HttpRouterFactoryOptions.getPath` is now optional as a default value is always provided in the factory. +- 71a5ec0f06: Updated usages of `LogMeta`. +- Updated dependencies + - @backstage/backend-plugin-api@0.4.0 + - @backstage/backend-common@0.18.2 + - @backstage/backend-tasks@0.4.3 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + - @backstage/config-loader@1.1.8 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.11 + - @backstage/plugin-permission-node@0.7.5 + +## 0.4.0-next.2 + +### Minor Changes + +- 01a075ec1d: **BREAKING**: Renamed `RootHttpRouterConfigureOptions` to `RootHttpRouterConfigureContext`, and removed the unused type `ServiceOrExtensionPoint`. +- 4ae71b7f2e: **BREAKING** Renaming `*Factory` exports to `*ServiceFactory` instead. For example `configFactory` now is exported as `configServiceFactory`. +- d31d8e00b3: **BREAKING** `HttpServerCertificateOptions` when specified with a `key` and `cert` should also have the `type: 'pem'` instead of `type: 'plain'` + +### Patch Changes + +- e716946103: Updated usage of the lifecycle service. +- f60cca9da1: Updated database factory to pass service deps required for restoring database state during development. +- 610d65e143: Updates to match new `BackendFeature` type. +- ab22515647: The shutdown signal handlers are now installed as part of the backend instance rather than the lifecycle service, and explicitly cause the process to exit. +- b729f9f31f: Moved the options of the `config` and `rootHttpRouter` services out to the factories themselves, where they belong +- 71a5ec0f06: Updated usages of `LogMeta`. +- Updated dependencies + - @backstage/backend-plugin-api@0.4.0-next.2 + - @backstage/backend-common@0.18.2-next.2 + - @backstage/backend-tasks@0.4.3-next.2 + - @backstage/plugin-auth-node@0.2.11-next.2 + - @backstage/plugin-permission-node@0.7.5-next.2 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + - @backstage/config-loader@1.1.8 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.18.2-next.1 + - @backstage/backend-plugin-api@0.3.2-next.1 + - @backstage/backend-tasks@0.4.3-next.1 + - @backstage/cli-common@0.1.11 + - @backstage/config@1.0.6 + - @backstage/config-loader@1.1.8 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.11-next.1 + - @backstage/plugin-permission-node@0.7.5-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- a18da2f8b5: Fixed an issue were the log redaction didn't properly escape RegExp characters. +- ed8b5967d7: `HttpRouterFactoryOptions.getPath` is now optional as a default value is always provided in the factory. +- Updated dependencies + - @backstage/backend-common@0.18.2-next.0 + - @backstage/backend-tasks@0.4.3-next.0 + - @backstage/plugin-auth-node@0.2.11-next.0 + - @backstage/plugin-permission-node@0.7.5-next.0 + - @backstage/backend-plugin-api@0.3.2-next.0 + +## 0.3.0 + +### Minor Changes + +- 02b119ff93: **BREAKING**: The `httpRouterFactory` now accepts a `getPath` option rather than `indexPlugin`. To set up custom index path, configure the new `rootHttpRouterFactory` with a custom `indexPath` instead. + + Added an implementation for the new `rootHttpRouterServiceRef`. + +### Patch Changes + +- ecc6bfe4c9: Use new `ServiceFactoryOrFunction` type. +- b99c030f1b: Moved over implementation of the root HTTP service from `@backstage/backend-common`, and replaced the `middleware` option with a `configure` callback option. +- 170282ece6: Fixed a bug in the default token manager factory where it created multiple incompatible instances. +- 843a0a158c: Added service factory for the new core identity service. +- 150a7dd790: An error will now be thrown if attempting to override the plugin metadata service. +- 483e907eaf: Internal updates of `createServiceFactory` from `@backstage/backend-plugin-api`. +- 015a6dced6: The `createSpecializedBackend` function will now throw an error if duplicate service implementations are provided. +- e3fca10038: Tweaked the plugin logger to use `plugin` as the label for the plugin ID, rather than `pluginId`. +- ecbec4ec4c: Internal refactor to match new options pattern in the experimental backend system. +- 51b7a7ed07: Exported the default root HTTP router implementation as `DefaultRootHttpRouter`. It only implements the routing layer and needs to be exposed via an HTTP server similar to the built-in setup in the `rootHttpRouterFactory`. +- 0e63aab311: Moved over logging and configuration loading implementations from `@backstage/backend-common`. There is a now `WinstonLogger` which implements the `RootLoggerService` through Winston with accompanying utilities. For configuration the `loadBackendConfig` function has been moved over, but it now instead returns an object with a `config` property. +- 8e06f3cf00: Switched imports of `loggerToWinstonLogger` to `@backstage/backend-common`. +- 3b8fd4169b: Internal folder structure refactor. +- 6cfd4d7073: Updated implementations for the new `RootLifecycleService`. +- Updated dependencies + - @backstage/backend-plugin-api@0.3.0 + - @backstage/backend-common@0.18.0 + - @backstage/backend-tasks@0.4.1 + - @backstage/config@1.0.6 + - @backstage/cli-common@0.1.11 + - @backstage/config-loader@1.1.8 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/plugin-auth-node@0.2.9 + - @backstage/plugin-permission-node@0.7.3 + +## 0.3.0-next.1 + +### Minor Changes + +- 02b119ff93: **BREAKING**: The `httpRouterFactory` now accepts a `getPath` option rather than `indexPlugin`. To set up custom index path, configure the new `rootHttpRouterFactory` with a custom `indexPath` instead. + + Added an implementation for the new `rootHttpRouterServiceRef`. + +### Patch Changes + +- ecc6bfe4c9: Use new `ServiceFactoryOrFunction` type. +- b99c030f1b: Moved over implementation of the root HTTP service from `@backstage/backend-common`, and replaced the `middleware` option with a `configure` callback option. +- 150a7dd790: An error will now be thrown if attempting to override the plugin metadata service. +- 015a6dced6: The `createSpecializedBackend` function will now throw an error if duplicate service implementations are provided. +- e3fca10038: Tweaked the plugin logger to use `plugin` as the label for the plugin ID, rather than `pluginId`. +- 8e06f3cf00: Switched imports of `loggerToWinstonLogger` to `@backstage/backend-common`. +- Updated dependencies + - @backstage/backend-plugin-api@0.3.0-next.1 + - @backstage/backend-common@0.18.0-next.1 + - @backstage/backend-tasks@0.4.1-next.1 + - @backstage/plugin-permission-node@0.7.3-next.1 + - @backstage/config@1.0.6-next.0 + - @backstage/errors@1.1.4 + +## 0.2.5-next.0 + +### Patch Changes + +- 6cfd4d7073: Updated implementations for the new `RootLifecycleService`. +- Updated dependencies + - @backstage/backend-plugin-api@0.2.1-next.0 + - @backstage/backend-common@0.18.0-next.0 + - @backstage/backend-tasks@0.4.1-next.0 + - @backstage/errors@1.1.4 + - @backstage/plugin-permission-node@0.7.3-next.0 + +## 0.2.4 + +### Patch Changes + +- cb1c2781c0: Updated logger implementations to match interface changes. +- 884d749b14: Refactored to use `coreServices` from `@backstage/backend-plugin-api`. +- afa3bf5657: Added `.stop()` method to `Backend`. +- d6dbf1792b: Added `lifecycleFactory` implementation. +- 05a928e296: Updated usages of types from `@backstage/backend-plugin-api`. +- 5260d8fc7d: Root scoped services are now always initialized, regardless of whether they're used by any features. +- Updated dependencies + - @backstage/backend-common@0.17.0 + - @backstage/backend-tasks@0.4.0 + - @backstage/plugin-permission-node@0.7.2 + - @backstage/errors@1.1.4 + - @backstage/backend-plugin-api@0.2.0 + +## 0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.4.0-next.3 + - @backstage/plugin-permission-node@0.7.2-next.3 + - @backstage/backend-common@0.17.0-next.3 + - @backstage/backend-plugin-api@0.2.0-next.3 + - @backstage/errors@1.1.4-next.1 + +## 0.2.4-next.2 + +### Patch Changes + +- 884d749b14: Refactored to use `coreServices` from `@backstage/backend-plugin-api`. +- Updated dependencies + - @backstage/backend-common@0.17.0-next.2 + - @backstage/backend-plugin-api@0.2.0-next.2 + - @backstage/backend-tasks@0.4.0-next.2 + - @backstage/plugin-permission-node@0.7.2-next.2 + - @backstage/errors@1.1.4-next.1 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.17.0-next.1 + - @backstage/backend-tasks@0.4.0-next.1 + - @backstage/backend-plugin-api@0.1.5-next.1 + - @backstage/plugin-permission-node@0.7.2-next.1 + - @backstage/errors@1.1.4-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- d6dbf1792b: Added `lifecycleFactory` implementation. +- Updated dependencies + - @backstage/backend-common@0.16.1-next.0 + - @backstage/plugin-permission-node@0.7.2-next.0 + - @backstage/backend-plugin-api@0.1.5-next.0 + - @backstage/backend-tasks@0.3.8-next.0 + - @backstage/errors@1.1.4-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0 + - @backstage/backend-tasks@0.3.7 + - @backstage/backend-plugin-api@0.1.4 + - @backstage/plugin-permission-node@0.7.1 + - @backstage/errors@1.1.3 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.1 + - @backstage/backend-plugin-api@0.1.4-next.1 + - @backstage/backend-tasks@0.3.7-next.1 + - @backstage/plugin-permission-node@0.7.1-next.1 + - @backstage/errors@1.1.3-next.0 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.2 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/errors@1.1.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/errors@1.1.2-next.0 + +## 0.2.1 + +### Patch Changes + +- 2c57c0c499: Made `ApiRef.defaultFactory` internal. +- 854ba37357: Updated to support new `ServiceFactory` formats. +- af6bb42c68: Updated `ServiceRegistry` to not initialize factories more than once. +- 409ed984e8: Updated service implementations and backend wiring to support scoped service. +- de3347ca74: Updated usages of `ServiceFactory`. +- 1f384c5644: Improved error messaging when failing to instantiate services. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.2 + - @backstage/backend-common@0.15.1 + - @backstage/plugin-permission-node@0.6.5 + - @backstage/backend-tasks@0.3.5 + - @backstage/errors@1.1.1 + +## 0.2.1-next.2 + +### Patch Changes + +- 854ba37357: Updated to support new `ServiceFactory` formats. +- 409ed984e8: Updated service implementations and backend wiring to support scoped service. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.2-next.2 + - @backstage/errors@1.1.1-next.0 + - @backstage/backend-common@0.15.1-next.3 + - @backstage/backend-tasks@0.3.5-next.1 + - @backstage/plugin-permission-node@0.6.5-next.3 + +## 0.2.1-next.1 + +### Patch Changes + +- 2c57c0c499: Made `ApiRef.defaultFactory` internal. +- af6bb42c68: Updated `ServiceRegistry` to not initialize factories more than once. +- 1f384c5644: Improved error messaging when failing to instantiate services. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.2-next.1 + - @backstage/backend-common@0.15.1-next.2 + - @backstage/plugin-permission-node@0.6.5-next.2 + +## 0.2.1-next.0 + +### Patch Changes + +- de3347ca74: Updated usages of `ServiceFactory`. +- Updated dependencies + - @backstage/backend-common@0.15.1-next.0 + - @backstage/backend-tasks@0.3.5-next.0 + - @backstage/backend-plugin-api@0.1.2-next.0 + - @backstage/plugin-permission-node@0.6.5-next.0 + +## 0.2.0 + +### Minor Changes + +- 5df230d48c: Introduced a new `backend-defaults` package carrying `createBackend` which was previously exported from `backend-app-api`. + The `backend-app-api` package now exports the `createSpecializedBacked` that does not add any service factories by default. + +### Patch Changes + +- 0599732ec0: Refactored experimental backend system with new type names. +- Updated dependencies + - @backstage/backend-common@0.15.0 + - @backstage/backend-plugin-api@0.1.1 + - @backstage/backend-tasks@0.3.4 + - @backstage/plugin-permission-node@0.6.4 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.0-next.0 + - @backstage/backend-tasks@0.3.4-next.0 + - @backstage/backend-plugin-api@0.1.1-next.0 + - @backstage/plugin-permission-node@0.6.4-next.0 + +## 0.1.0 + +### Minor Changes + +- 91c1d12123: Add initial plumbing for creating backends using the experimental backend framework. + + This package is highly **EXPERIMENTAL** and should not be used in production. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.1.0 + - @backstage/backend-common@0.14.1 + - @backstage/plugin-permission-node@0.6.3 + - @backstage/backend-tasks@0.3.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 91c1d12123: Add initial plumbing for creating backends using the experimental backend framework. + + This package is highly **EXPERIMENTAL** and should not be used in production. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.1.0-next.0 + - @backstage/backend-common@0.14.1-next.3 + - @backstage/plugin-permission-node@0.6.3-next.2 + - @backstage/backend-tasks@0.3.3-next.3 + +## @backstage/frontend-app-api@0.7.0-next.2 + +### Minor Changes + +- ddddecb: Extensions in app-config now always affect ordering. Previously, only when enabling disabled extensions did they rise to the top. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/integration@1.11.0-next.0 + +### Minor Changes + +- 2cc750d: Added `HarnessIntegration` via the `ScmIntegrations` interface. + +## @backstage/repo-tools@0.9.0-next.2 + +### Minor Changes + +- 683870a: Adds 2 new commands `repo schema openapi diff` and `package schema openapi diff`. `repo schema openapi diff` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes. They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-catalog@1.20.0-next.2 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- 4118530: Avoiding pre-loading display total count undefined for table counts +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/plugin-catalog-backend@1.22.0-next.2 + +### Minor Changes + +- f2a2a83: Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`. +- f2a2a83: The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-node@1.12.0-next.2 + +### Minor Changes + +- f2a2a83: Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`. +- f2a2a83: Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden. + +## @backstage/plugin-catalog-react@1.12.0-next.2 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0-next.2 + +### Minor Changes + +- 18f736f: Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests + +### Patch Changes + +- 8fa8a00: Add merge method and squash option for project creation +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/backend-common@0.22.0-next.2 + +### Patch Changes + +- 2cc750d: Added `HarnessURLReader` with `readUrl` support. +- ccc8851: Added config prop `ensureSchemaExists` to support postgres instances where user can create schemas but not databases. +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + +## @backstage/backend-defaults@0.2.18-next.2 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## @backstage/backend-dynamic-feature-service@0.2.10-next.2 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## @backstage/backend-test-utils@0.3.8-next.2 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## @backstage/cli@0.26.5-next.1 + +### Patch Changes + +- 2a6f10d: The `versions:bump` command will no longer exit with a non-zero status if the version bump fails due to forbidden duplicate package installations. It will now also provide more information about how to troubleshoot such an error. The set of forbidden duplicates has also been expanded to include all `@backstage/*-app-api` packages. +- c5d7b40: Allow passing a `--require` argument through to the Node process during `package start` +- cc3c518: Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows +- 812dff0: Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`. +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + +## @backstage/core-components@0.14.7-next.2 + +### Patch Changes + +- a2ee4df: Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines. + Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card. + +## @backstage/create-app@0.5.15-next.2 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.0.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/frontend-test-utils@0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.0-next.2 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/integration-react@1.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-api-docs@0.11.5-next.2 + +### Patch Changes + +- 725ff0b: Fix dark mode text color inside tables in `description:` from OpenAPI definitions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/plugin-auth-backend@0.22.5-next.2 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.2 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.2 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-auth-backend@0.22.5-next.2 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.2 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules + +## @backstage/plugin-bitbucket-cloud-common@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.19-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.6.1-next.2 + +### Patch Changes + +- 0b50143: GitHub push events now schedule a refresh on entities that have a `refresh_key` matching the `catalogPath` config itself. + This allows to support a `catalogPath` configuration that uses glob patterns. +- f2a2a83: Updated to use the new `catalogAnalysisExtensionPoint` API. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-catalog-backend-module-github@0.6.1-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.4 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.2 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-catalog-graph@0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/plugin-catalog-import@0.10.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/plugin-devtools-backend@0.3.4-next.2 + +### Patch Changes + +- 036feca: Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-events-node@0.3.4-next.2 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used + +## @backstage/plugin-home@0.7.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/plugin-kubernetes@0.11.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + +## @backstage/plugin-kubernetes-backend@0.17.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-kubernetes-cluster@0.0.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + +## @backstage/plugin-notifications@0.2.1-next.2 + +### Patch Changes + +- 42eaf63: Increase default and allow modifying notification snackbar auto hide duration +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + +## @backstage/plugin-notifications-backend@0.2.1-next.2 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## @backstage/plugin-notifications-backend-module-email@0.0.1-next.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- e538b10: Support relative links in notifications sent via email +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-org@0.6.25-next.2 + +### Patch Changes + +- 99e6105: Fix ownership card sometimes locking up for complex org structures +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/plugin-org-react@0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + +## @backstage/plugin-scaffolder@1.19.4-next.2 + +### Patch Changes + +- 762141c: Fixed a bug where the `MultiEntityPicker` was not able to be set as required +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-scaffolder-react@1.8.5-next.2 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/plugin-scaffolder-backend@1.22.6-next.2 + +### Patch Changes + +- e4b50ab: Scaffolder workspace serialization +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.2 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.1-next.0 + +### Patch Changes + +- 503d769: Add a new scaffolder action to allow sending notifications from templates +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-node@0.4.4-next.2 + +### Patch Changes + +- e4b50ab: Scaffolder workspace serialization +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-scaffolder-react@1.8.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + +## @backstage/plugin-search@1.4.11-next.2 + +### Patch Changes + +- 0501243: Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + +## @backstage/plugin-search-backend@1.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/repo-tools@0.9.0-next.2 + +## @backstage/plugin-search-backend-module-catalog@0.1.24-next.2 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + +## @backstage/plugin-search-backend-module-techdocs@0.1.23-next.2 + +### Patch Changes + +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-techdocs-node@1.12.4-next.2 + +## @backstage/plugin-signals-backend@0.1.4-next.2 + +### Patch Changes + +- 845d56a: Improved signal lifecycle management and added server side pinging of connections +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + +## @backstage/plugin-techdocs@1.10.5-next.2 + +### Patch Changes + +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/plugin-techdocs-backend@1.10.5-next.2 + +### Patch Changes + +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.2 + - @backstage/plugin-techdocs-node@1.12.4-next.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/integration-react@1.1.27-next.0 + +## @backstage/plugin-techdocs-node@1.12.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + +## @backstage/plugin-user-settings@0.8.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## example-app@0.2.97-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.5-next.1 + - @backstage/frontend-app-api@0.7.0-next.2 + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-notifications@0.2.1-next.2 + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/plugin-api-docs@0.11.5-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/plugin-scaffolder@1.19.4-next.2 + - @backstage/plugin-search@1.4.11-next.2 + - @backstage/plugin-org@0.6.25-next.2 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.2 + - @backstage/plugin-catalog-import@0.10.11-next.2 + - @backstage/plugin-home@0.7.4-next.2 + - @backstage/plugin-kubernetes@0.11.10-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.2 + - @backstage/plugin-scaffolder-react@1.8.5-next.2 + - @backstage/plugin-user-settings@0.8.6-next.2 + - @backstage/integration-react@1.1.27-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.2 + +## example-app-next@0.0.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.5-next.1 + - @backstage/frontend-app-api@0.7.0-next.2 + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-notifications@0.2.1-next.2 + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/plugin-api-docs@0.11.5-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/plugin-scaffolder@1.19.4-next.2 + - @backstage/plugin-search@1.4.11-next.2 + - @backstage/plugin-org@0.6.25-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.2 + - @backstage/plugin-catalog-import@0.10.11-next.2 + - @backstage/plugin-home@0.7.4-next.2 + - @backstage/plugin-kubernetes@0.11.10-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.2 + - @backstage/plugin-scaffolder-react@1.8.5-next.2 + - @backstage/plugin-user-settings@0.8.6-next.2 + - @backstage/integration-react@1.1.27-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.2 + +## example-backend-legacy@0.2.98-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-devtools-backend@0.3.4-next.2 + - @backstage/plugin-scaffolder-backend@1.22.6-next.2 + - @backstage/plugin-signals-backend@0.1.4-next.2 + - @backstage/plugin-auth-backend@0.22.5-next.2 + - @backstage/plugin-techdocs-backend@1.10.5-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.2 + - @backstage/plugin-kubernetes-backend@0.17.1-next.2 + - @backstage/plugin-search-backend@1.5.8-next.2 + - example-app@0.2.97-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.2 + +## techdocs-cli-embedded-app@0.2.96-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.5-next.1 + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration-react@1.1.27-next.0 diff --git a/package.json b/package.json index 45bc0a2973..8611c9d59b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.27.0-next.1", + "version": "1.27.0-next.2", "private": true, "repository": { "type": "git", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index ab2c13c42a..890f1ca20b 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,34 @@ # example-app-next +## 0.0.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.5-next.1 + - @backstage/frontend-app-api@0.7.0-next.2 + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-notifications@0.2.1-next.2 + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/plugin-api-docs@0.11.5-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/plugin-scaffolder@1.19.4-next.2 + - @backstage/plugin-search@1.4.11-next.2 + - @backstage/plugin-org@0.6.25-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.2 + - @backstage/plugin-catalog-import@0.10.11-next.2 + - @backstage/plugin-home@0.7.4-next.2 + - @backstage/plugin-kubernetes@0.11.10-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.2 + - @backstage/plugin-scaffolder-react@1.8.5-next.2 + - @backstage/plugin-user-settings@0.8.6-next.2 + - @backstage/integration-react@1.1.27-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.2 + ## 0.0.11-next.1 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index d41ff02488..d7b286e0fa 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.11-next.1", + "version": "0.0.11-next.2", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 12e71ba6c0..3b8d6ca936 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,32 @@ # example-app +## 0.2.97-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.5-next.1 + - @backstage/frontend-app-api@0.7.0-next.2 + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-notifications@0.2.1-next.2 + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/plugin-api-docs@0.11.5-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/plugin-scaffolder@1.19.4-next.2 + - @backstage/plugin-search@1.4.11-next.2 + - @backstage/plugin-org@0.6.25-next.2 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.2 + - @backstage/plugin-catalog-import@0.10.11-next.2 + - @backstage/plugin-home@0.7.4-next.2 + - @backstage/plugin-kubernetes@0.11.10-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.2 + - @backstage/plugin-scaffolder-react@1.8.5-next.2 + - @backstage/plugin-user-settings@0.8.6-next.2 + - @backstage/integration-react@1.1.27-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.2 + ## 0.2.97-next.1 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 20d0d0312f..13466e1b41 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.97-next.1", + "version": "0.2.97-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index bb53e78ff1..f188b4aa51 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.7.2-next.1", + "version": "0.7.3-next.1", "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 c8e459ba78..3ab27aa3bf 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-common +## 0.22.0-next.2 + +### Patch Changes + +- 2cc750d: Added `HarnessURLReader` with `readUrl` support. +- ccc8851: Added config prop `ensureSchemaExists` to support postgres instances where user can create schemas but not databases. +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + ## 0.22.0-next.1 ### Minor Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 42c4c49863..52ae56cff4 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.22.0-next.1", + "version": "0.22.0-next.2", "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 a9871d0aa5..11a9e78a18 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-defaults +## 0.2.18-next.2 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + ## 0.2.18-next.1 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 69394ef5ba..ec26ec6471 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.2.18-next.1", + "version": "0.2.18-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index f45e57bd9c..a0f08195b1 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-dynamic-feature-service +## 0.2.10-next.2 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + ## 0.2.10-next.1 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 35d053d40a..715ca5f44d 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.10-next.1", + "version": "0.2.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index f6d199c6df..c4a59512e5 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,31 @@ # example-backend-legacy +## 0.2.98-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-devtools-backend@0.3.4-next.2 + - @backstage/plugin-scaffolder-backend@1.22.6-next.2 + - @backstage/plugin-signals-backend@0.1.4-next.2 + - @backstage/plugin-auth-backend@0.22.5-next.2 + - @backstage/plugin-techdocs-backend@1.10.5-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.2 + - @backstage/plugin-kubernetes-backend@0.17.1-next.2 + - @backstage/plugin-search-backend@1.5.8-next.2 + - example-app@0.2.97-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.2 + ## 0.2.98-next.1 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index 47c04f9a3c..799387f5d2 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.98-next.1", + "version": "0.2.98-next.2", "backstage": { "role": "backend" }, diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 6602f4cbe8..aafed2071e 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-test-utils +## 0.3.8-next.2 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + ## 0.3.8-next.1 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index fca8ef5c01..f9d591e470 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.3.8-next.1", + "version": "0.3.8-next.2", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 6c37772f73..804966ba3b 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/cli +## 0.26.5-next.1 + +### Patch Changes + +- 2a6f10d: The `versions:bump` command will no longer exit with a non-zero status if the version bump fails due to forbidden duplicate package installations. It will now also provide more information about how to troubleshoot such an error. The set of forbidden duplicates has also been expanded to include all `@backstage/*-app-api` packages. +- c5d7b40: Allow passing a `--require` argument through to the Node process during `package start` +- cc3c518: Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows +- 812dff0: Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`. +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + ## 0.26.5-next.0 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index c8baae4d4d..d6fe116248 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.26.5-next.0", + "version": "0.26.5-next.1", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 2c6d4ea490..dc31cb9f4d 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/core-components +## 0.14.7-next.2 + +### Patch Changes + +- a2ee4df: Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines. + Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card. + ## 0.14.6-next.1 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 87f51dff26..014059b89b 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.6-next.1", + "version": "0.14.7-next.2", "publishConfig": { "access": "public" }, diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index c6338c7243..b301828207 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/create-app +## 0.5.15-next.2 + +### Patch Changes + +- Bumped create-app version. + ## 0.5.15-next.1 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 72d8e00045..ec0e764fff 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.15-next.1", + "version": "0.5.15-next.2", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index e4622aab60..673ee43d24 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/dev-utils +## 1.0.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration-react@1.1.27-next.0 + ## 1.0.32-next.1 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index bac82890d0..f6308b6446 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.32-next.1", + "version": "1.0.32-next.2", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 0ac06f4393..c560948cc2 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/frontend-app-api +## 0.7.0-next.2 + +### Minor Changes + +- ddddecb: Extensions in app-config now always affect ordering. Previously, only when enabling disabled extensions did they rise to the top. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.6.5-next.1 ### Patch Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 65045bc6ab..7602f688ef 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.6.5-next.1", + "version": "0.7.0-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 79d54b2f29..0d41932eb0 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/frontend-test-utils +## 0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.0-next.2 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.1.7-next.1 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index af83141368..44eddc2ada 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.1.7-next.1", + "version": "0.1.7-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index b12e621a60..33de746f92 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/integration-react +## 1.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + ## 1.1.26 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index c77bd26f27..5d87ccaa13 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.1.26", + "version": "1.1.27-next.0", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 8a4616bfd7..0ed0080b10 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/integration +## 1.11.0-next.0 + +### Minor Changes + +- 2cc750d: Added `HarnessIntegration` via the `ScmIntegrations` interface. + ## 1.10.0 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index bbd0eecbe7..ad41e216a7 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.10.0", + "version": "1.11.0-next.0", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 443a5f30b5..b3aefc9e67 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/repo-tools +## 0.9.0-next.2 + +### Minor Changes + +- 683870a: Adds 2 new commands `repo schema openapi diff` and `package schema openapi diff`. `repo schema openapi diff` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes. They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + ## 0.8.1-next.1 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index e39e5203b7..14a2cbca1d 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.8.1-next.1", + "version": "0.9.0-next.2", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 99853ed312..cd27b1c1a7 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,16 @@ # techdocs-cli-embedded-app +## 0.2.96-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.5-next.1 + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration-react@1.1.27-next.0 + ## 0.2.96-next.1 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index ce19840c56..687a469914 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.96-next.1", + "version": "0.2.96-next.2", "private": true, "backstage": { "role": "frontend" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index faac54a107..39613bd409 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-api-docs +## 0.11.5-next.2 + +### Patch Changes + +- 725ff0b: Fix dark mode text color inside tables in `description:` from OpenAPI definitions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.11.5-next.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index c8acdf256f..4b8ff68d86 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.5-next.1", + "version": "0.11.5-next.2", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin" diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index 0b2fa1faf0..784334ac96 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.10-next.2 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-auth-backend@0.22.5-next.2 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index ae25459ccc..86d4793d81 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", "description": "The aws-alb provider module for the Backstage auth backend.", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index c9da8b5970..32ea1205d9 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.1.15-next.2 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules + ## 0.1.15-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 0e8db4d1a5..3f587ad033 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.1.15-next.1", + "version": "0.1.15-next.2", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index aa33d39713..59a7aa4af0 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend +## 0.22.5-next.2 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.2 + ## 0.22.5-next.1 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 372b8f8d4b..910c7d4180 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.5-next.1", + "version": "0.22.5-next.2", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin" diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 64ffaa12dd..1397e67af4 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.11.0-next.0 + ## 0.2.18 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 41ba69be10..dba2dbc40f 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.2.18", + "version": "0.2.19-next.0", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library" diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index be6e743eb9..f0e0b8e6aa 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.3.13-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index fbc4122fe2..d80383a18f 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.3.13-next.1", + "version": "0.3.13-next.2", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 5b7751dc03..6d3af364bb 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.38-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 4df7824fa7..a0a7edb728 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.1.38-next.1", + "version": "0.1.38-next.2", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index 449c016837..fc48605090 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + ## 0.2.1-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 86c7bb02e7..408fdfcc4f 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.2.1-next.1", + "version": "0.2.1-next.2", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index a40ceb8254..8884d911d3 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.19-next.0 + ## 0.2.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index f6b4620671..a6554c173a 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.2.5-next.1", + "version": "0.2.5-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 153494a358..424f143da1 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.32-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index e46d0107a5..e1b0e4c362 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.32-next.1", + "version": "0.1.32-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 18299d760a..095366892f 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + ## 0.1.19-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index c58c014b0e..758a909b3c 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.1.19-next.1", + "version": "0.1.19-next.2", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 8b5e5edd96..3aaa06ae02 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.35-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index c436a07263..f58d6bb85f 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.35-next.1", + "version": "0.1.35-next.2", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index b1fd9b17d7..01a6fb3b7a 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-catalog-backend-module-github@0.6.1-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + ## 0.1.13-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 2838396c88..4557cbedef 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.1.13-next.1", + "version": "0.1.13-next.2", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 5bb58f25c0..ad4281d185 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.1-next.2 + +### Patch Changes + +- 0b50143: GitHub push events now schedule a refresh on entities that have a `refresh_key` matching the `catalogPath` config itself. + This allows to support a `catalogPath` configuration that uses glob patterns. +- f2a2a83: Updated to use the new `catalogAnalysisExtensionPoint` API. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.6.1-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 47a32566af..05306549c3 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.6.1-next.1", + "version": "0.6.1-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index fa0b9f2a24..4926c5efc4 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.0.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.4 + ## 0.0.1-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index c2d524d9d8..e0eb268ba6 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.0.1-next.1", + "version": "0.0.1-next.2", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 5b36f2570b..661d422547 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.15-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.3.15-next.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index d0d3c876ae..c485326f3e 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -72,5 +72,5 @@ ] } }, - "version": "0.3.15-next.3" + "version": "0.3.15-next.4" } diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 2f4505abba..961ebe081c 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + ## 0.4.23-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 6c9f930d33..0a4f73c5d4 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.4.23-next.1", + "version": "0.4.23-next.2", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 10a774ffad..22da3d6099 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + ## 0.5.34-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 82558bc6d8..e50d629e0e 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.5.34-next.1", + "version": "0.5.34-next.2", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 8b7dcb9c80..4d6a4bc350 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + ## 0.5.26-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 7a89b0c507..ddc3ca4af3 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.5.26-next.1", + "version": "0.5.26-next.2", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index f7303d298e..2b1e0650a8 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-catalog-backend@1.22.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.36-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index be2efd8c93..2eb832684d 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.1.36-next.1", + "version": "0.1.36-next.2", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index b2604c469e..84d705b8be 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + ## 0.1.24-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 6c38b658e3..6d3a71092e 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.1.24-next.1", + "version": "0.1.24-next.2", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index b7b46ed403..b51aacb4f1 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + ## 0.1.16-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 6b8bc12493..2bf29a3bb3 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.1.16-next.1", + "version": "0.1.16-next.2", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index a53e36a696..1a2e2a2785 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.5-next.2 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + ## 0.4.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 38d96e62e9..ba13df5a2a 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.4.5-next.1", + "version": "0.4.5-next.2", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 426333c567..4d8e2f1cd0 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend +## 1.22.0-next.2 + +### Minor Changes + +- f2a2a83: Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`. +- f2a2a83: The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 1.22.0-next.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index cea3f06c6f..f57efe5ae6 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.22.0-next.1", + "version": "1.22.0-next.2", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin" diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index e1b79d8d13..372a94a19c 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-graph +## 0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.4.5-next.1 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index d0363f5376..ac9039a441 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.5-next.1", + "version": "0.4.5-next.2", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index bfdfabba5a..f201bf7552 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-import +## 0.10.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.27-next.0 + ## 0.10.11-next.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index a766110a4d..d5f70964a1 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.10.11-next.1", + "version": "0.10.11-next.2", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index a2405a7eef..949a44b384 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-node +## 1.12.0-next.2 + +### Minor Changes + +- f2a2a83: Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`. +- f2a2a83: Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden. + ## 1.11.2-next.1 ### Patch Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index ebfea63cef..010389079d 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.11.2-next.1", + "version": "1.12.0-next.2", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library" diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 190773044a..fe64a6101d 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-react +## 1.12.0-next.2 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.27-next.0 + ## 1.11.4-next.1 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 041253f223..85b7ba95be 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.11.4-next.1", + "version": "1.12.0-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 300083da1c..d20331bbfe 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog +## 1.20.0-next.2 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- 4118530: Avoiding pre-loading display total count undefined for table counts +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/integration-react@1.1.27-next.0 + ## 1.19.1-next.1 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 207717e1c9..e32191c629 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.19.1-next.1", + "version": "1.20.0-next.2", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 1d4247cb9f..38e0da331f 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-devtools-backend +## 0.3.4-next.2 + +### Patch Changes + +- 036feca: Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + ## 0.3.4-next.1 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index e9c6f48b49..fd2c8eac4a 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.4-next.1", + "version": "0.3.4-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 185da613eb..7238c9ed49 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-events-node +## 0.3.4-next.2 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used + ## 0.3.4-next.1 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 7312950a99..9500fdc0ad 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.3.4-next.1", + "version": "0.3.4-next.2", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library" diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 565d82f395..6fb3fbaec9 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-home +## 0.7.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.7.4-next.1 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 0058c35e00..63f01e84c4 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.4-next.1", + "version": "0.7.4-next.2", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index a1000aea2c..48e205f868 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes-backend +## 0.17.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + ## 0.17.1-next.1 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index a8634d8855..24a52bc8a8 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.17.1-next.1", + "version": "0.17.1-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 8ebfd45d77..09adcabc63 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + ## 0.0.11-next.1 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 1402c79951..409c385263 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.11-next.1", + "version": "0.0.11-next.2", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index de0f70736a..87b1132710 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes +## 0.11.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + ## 0.11.10-next.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index e75d503558..bfb8ed1e2c 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.10-next.1", + "version": "0.11.10-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 065f8cb533..1c6e150a29 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-notifications-backend-module-email +## 0.0.1-next.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- e538b10: Support relative links in notifications sent via email +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + ## 0.0.1-next.0 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index bb99d9cb97..1884dc6352 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.0.1-next.0", + "version": "0.0.1-next.1", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 389fa25f61..5d6792659d 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-notifications-backend +## 0.2.1-next.2 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + ## 0.2.1-next.1 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 9f7f427895..3aada48a9c 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.2.1-next.1", + "version": "0.2.1-next.2", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 78c8c96f1d..13cff2c7f7 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-notifications +## 0.2.1-next.2 + +### Patch Changes + +- 42eaf63: Increase default and allow modifying notification snackbar auto hide duration +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + ## 0.2.1-next.1 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index bdc96a921b..09127ecf04 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.1-next.1", + "version": "0.2.1-next.2", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 2ad74d1db0..8767b0e303 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-org-react +## 0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + ## 0.1.24-next.1 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 0567691a3b..53f8f83fe2 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.24-next.1", + "version": "0.1.24-next.2", "backstage": { "role": "web-library" }, diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 3d2bedca68..a18e9a4e61 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org +## 0.6.25-next.2 + +### Patch Changes + +- 99e6105: Fix ownership card sometimes locking up for complex org structures +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.6.25-next.1 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index b00e6f2bb2..719eef72ce 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.25-next.1", + "version": "0.6.25-next.2", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin" diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index 37987ca19f..cdbe8cd77e 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index efd61bae83..d9d648ec6d 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 099d4d6fd0..93fc9f821f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 51df2f87ed..d58a871f99 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 83a345f463..9b083cfaf4 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 2c195c8d88..b75099e81d 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 31b5cfad46..5067f00aa8 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.2 + ## 0.2.8-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index ad07c7208d..ce82b551b4 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.2.8-next.1", + "version": "0.2.8-next.2", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 1bbc8a234d..23a7973822 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.2.19-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index ddf56c91d1..f33e71b536 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.19-next.1", + "version": "0.2.19-next.2", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 01535ca917..7220f00aa1 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.2.42-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index b4206baff2..78b9a2a8ce 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.42-next.1", + "version": "0.2.42-next.2", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index 959d01271b..fd11a0f755 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index d7f9bfce66..7215239608 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 306fbd6812..c88d71f652 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 77bc3cd26e..c49954b6b9 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.8-next.1", + "version": "0.1.8-next.2", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index bcef59a58c..39454530eb 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.2.8-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 8491b5877a..a593541e08 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.2.8-next.1", + "version": "0.2.8-next.2", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 58f4cfdfb0..18efb70009 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.0-next.2 + +### Minor Changes + +- 18f736f: Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests + +### Patch Changes + +- 8fa8a00: Add merge method and squash option for project creation +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.3.4-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 941dcb5aef..d03fb65c1b 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.3.4-next.1", + "version": "0.4.0-next.2", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md new file mode 100644 index 0000000000..9a1c1b8891 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -0,0 +1,10 @@ +# @backstage/plugin-scaffolder-backend-module-notifications + +## 0.0.1-next.0 + +### Patch Changes + +- 503d769: Add a new scaffolder action to allow sending notifications from templates +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 11974049a1..7cb996f72b 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.0.0", + "version": "0.0.1-next.0", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 4553938eab..f721032c0a 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.4.35-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index a523ed9a8d..f8e1289359 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.35-next.1", + "version": "0.4.35-next.2", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 0ed790e3be..265517070d 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-scaffolder-backend +## 1.22.6-next.2 + +### Patch Changes + +- e4b50ab: Scaffolder workspace serialization +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.4-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.2 + ## 1.22.5-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 3492901386..cf3d3d1ca0 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.22.5-next.1", + "version": "1.22.6-next.2", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index f1739c59e4..d9e2804297 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node +## 0.4.4-next.2 + +### Patch Changes + +- e4b50ab: Scaffolder workspace serialization +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + ## 0.4.4-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index f0776e6491..11d3da67bc 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.4-next.1", + "version": "0.4.4-next.2", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library" diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 9c3a25638d..ca23ab25be 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-react +## 1.8.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + ## 1.8.5-next.1 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index b7b7b20014..728c6e23d7 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.8.5-next.1", + "version": "1.8.5-next.2", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library" diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 9a7ecd2e50..6edfc1484d 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder +## 1.19.4-next.2 + +### Patch Changes + +- 762141c: Fixed a bug where the `MultiEntityPicker` was not able to be set as required +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-scaffolder-react@1.8.5-next.2 + - @backstage/integration-react@1.1.27-next.0 + ## 1.19.4-next.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 14c1d5b2de..fe0deb9036 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.19.4-next.1", + "version": "1.19.4-next.2", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin" diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index fb9977e0ea..6ed9030a22 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-catalog +## 0.1.24-next.2 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + ## 0.1.24-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index ad03071eaf..aef1a1028d 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.24-next.1", + "version": "0.1.24-next.2", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index e79c98ab5a..850db158c8 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.1.23-next.2 + +### Patch Changes + +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0-next.2 + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-techdocs-node@1.12.4-next.2 + ## 0.1.23-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 09aa136f0e..5cf53c40be 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.23-next.1", + "version": "0.1.23-next.2", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 5dabd9f1a5..c4bc71318c 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend +## 1.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/repo-tools@0.9.0-next.2 + ## 1.5.8-next.1 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index c06b99ec92..dde1dc9de9 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.8-next.1", + "version": "1.5.8-next.2", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin" diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index cde6f55c14..2b221c75e9 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search +## 1.4.11-next.2 + +### Patch Changes + +- 0501243: Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + ## 1.4.11-next.1 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 06f5ff47e7..f064be1433 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.11-next.1", + "version": "1.4.11-next.2", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin" diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index fcd34ead2e..5287438fb6 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-signals-backend +## 0.1.4-next.2 + +### Patch Changes + +- 845d56a: Improved signal lifecycle management and added server side pinging of connections +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/plugin-events-node@0.3.4-next.2 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 24561caf46..b6f16c272a 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.4-next.1", + "version": "0.1.4-next.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 5151796171..00c7297773 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.5-next.2 + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/plugin-catalog@1.20.0-next.2 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/integration-react@1.1.27-next.0 + ## 1.0.32-next.1 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 6a6edcd5fa..e1ee4400bb 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.32-next.1", + "version": "1.0.32-next.2", "backstage": { "role": "web-library" }, diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 53ee8b3681..d6847da1ae 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-backend +## 1.10.5-next.2 + +### Patch Changes + +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.2 + - @backstage/plugin-techdocs-node@1.12.4-next.2 + ## 1.10.5-next.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 013029bb0b..79a701413a 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.5-next.1", + "version": "1.10.5-next.2", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin" diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index b914a8e3b1..77e83144d5 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/integration-react@1.1.27-next.0 + ## 1.1.10-next.1 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 5c8a0248ee..8c89c9bf37 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.10-next.1", + "version": "1.1.10-next.2", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module" diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 23f5d333d3..aea92cb71c 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-techdocs-node +## 1.12.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.2 + - @backstage/integration@1.11.0-next.0 + ## 1.12.4-next.1 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 1ff7b305f6..0eda77c887 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.4-next.1", + "version": "1.12.4-next.2", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index ee3071b030..5011b3471c 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-techdocs +## 1.10.5-next.2 + +### Patch Changes + +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/integration@1.11.0-next.0 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/integration-react@1.1.27-next.0 + ## 1.10.5-next.1 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 7e06c8f128..d75774cb10 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.10.5-next.1", + "version": "1.10.5-next.2", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin" diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 2611a7d18a..01d7c1214c 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-user-settings +## 0.8.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + - @backstage/core-compat-api@0.2.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.8.6-next.1 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 3461d220d6..1af0b2fb41 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.6-next.1", + "version": "0.8.6-next.2", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin" diff --git a/yarn.lock b/yarn.lock index 126129567f..6cee25b42b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4337,7 +4337,25 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@^1.1.26, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": +"@backstage/integration-react@npm:^1.1.26": + version: 1.1.26 + resolution: "@backstage/integration-react@npm:1.1.26" + dependencies: + "@backstage/config": ^1.2.0 + "@backstage/core-plugin-api": ^1.9.2 + "@backstage/integration": ^1.10.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@types/react": ^16.13.1 || ^17.0.0 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: 590e8293a0e21a034126c1a00c1c69c66bba81dcbf39675336c092b250ee139effe874e443a99521751b3c1aa9b103603bc8a3177a9f115ff0f1a0249ac5eed6 + languageName: node + linkType: hard + +"@backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -4361,6 +4379,23 @@ __metadata: languageName: unknown linkType: soft +"@backstage/integration@npm:^1.10.0": + version: 1.10.0 + resolution: "@backstage/integration@npm:1.10.0" + dependencies: + "@azure/identity": ^4.0.0 + "@backstage/config": ^1.2.0 + "@backstage/errors": ^1.2.4 + "@octokit/auth-app": ^4.0.0 + "@octokit/rest": ^19.0.3 + cross-fetch: ^4.0.0 + git-url-parse: ^14.0.0 + lodash: ^4.17.21 + luxon: ^3.0.0 + checksum: 86324df95b30ff6ae92fcc605bd21d0f12cdc0553d555ebe8977a1be6554819ad8723eabcd99d1574c7c244b4822a6628d01273557040c89360394ba3198f6b9 + languageName: node + linkType: hard + "@backstage/integration@workspace:^, @backstage/integration@workspace:packages/integration": version: 0.0.0-use.local resolution: "@backstage/integration@workspace:packages/integration" From 0ec0796d18f80a179c653ca307be174a23f1a4e8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 7 May 2024 17:57:51 +0200 Subject: [PATCH 293/567] backend-common: fix plugin auth for mixed usage of old and new system Signed-off-by: Patrik Oldsberg --- .changeset/few-vans-cross.md | 5 ++++ packages/backend-common/src/legacy.ts | 38 +++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .changeset/few-vans-cross.md diff --git a/.changeset/few-vans-cross.md b/.changeset/few-vans-cross.md new file mode 100644 index 0000000000..d7a12f6c10 --- /dev/null +++ b/.changeset/few-vans-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Plugins created through the `legacyPlugin` helper are now able to authenticate requests from plugins that are fully implemented using the new backend system. This fixes the `Key for the ES256 algorithm must be one of type KeyObject or CryptoKey. Received an instance of Uint8Array` error. diff --git a/packages/backend-common/src/legacy.ts b/packages/backend-common/src/legacy.ts index df1030e6b6..6aa991489c 100644 --- a/packages/backend-common/src/legacy.ts +++ b/packages/backend-common/src/legacy.ts @@ -15,6 +15,7 @@ */ import { + AuthService, coreServices, createBackendPlugin, ServiceRef, @@ -22,6 +23,7 @@ import { import { RequestHandler } from 'express'; import { cacheToPluginCacheManager } from './cache'; import { loggerToWinstonLogger } from './logging'; +import { TokenManager } from './tokens'; /** * @public @@ -38,6 +40,31 @@ type TransformedEnv< : TEnv[key]; }; +// Since the plugin will be using the new system our callers will expect us to support the +// new plugin tokens, which we'll also be signaling by supporting the JWKS endpoint through +// the http router. +// This makes sure that we accept the new plugin tokens as valid tokens, but otherwise fall +// back to whatever the token manager is doing. +function wrapTokenManager(tokenManager: TokenManager, auth: AuthService) { + return { + async getToken() { + return tokenManager.getToken(); + }, + async authenticate(token) { + if (token) { + // Unless it's a valid service token, we'll let the token manager do + // validation. We'll throw if we for example receive an invalid user + // token here, but that's what the token manager does too. + const credentials = await auth.authenticate(token); + if (auth.isPrincipal(credentials, 'service')) { + return; + } + } + await tokenManager.authenticate(token); + }, + } satisfies TokenManager; +} + /** * Creates a new custom plugin compatibility wrapper. * @@ -64,8 +91,12 @@ export function makeLegacyPlugin< pluginId: name, register(env) { env.registerInit({ - deps: { ...envMapping, _router: coreServices.httpRouter }, - async init({ _router, ...envDeps }) { + deps: { + ...envMapping, + _router: coreServices.httpRouter, + _auth: coreServices.auth, + }, + async init({ _router, _auth, ...envDeps }) { const { default: createRouter } = await createRouterImport; const pluginEnv = Object.fromEntries( Object.entries(envDeps).map(([key, dep]) => { @@ -73,6 +104,9 @@ export function makeLegacyPlugin< if (transform) { return [key, transform(dep)]; } + if (key === 'tokenManager') { + return [key, wrapTokenManager(dep as TokenManager, _auth)]; + } return [key, dep]; }), ); From 2ba6e52f40ecc2b8b12bc1cd4505b903be1b2074 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Tue, 7 May 2024 12:02:17 -0400 Subject: [PATCH 294/567] chore: remove action read scaffolder permission Signed-off-by: Frank Kong --- .../scaffolder-backend/src/service/router.ts | 34 ++--------------- plugins/scaffolder-common/src/permissions.ts | 37 ++++++------------- 2 files changed, 15 insertions(+), 56 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 759c5ca8d7..384b9f5340 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -30,12 +30,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { - InputError, - NotAllowedError, - NotFoundError, - stringifyError, -} from '@backstage/errors'; +import { InputError, NotFoundError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { @@ -56,7 +51,6 @@ import { templateParameterReadPermission, templateStepReadPermission, scaffolderTaskPermissions, - actionReadPermission, } from '@backstage/plugin-scaffolder-common/alpha'; import express from 'express'; import Router from 'express-promise-router'; @@ -78,10 +72,7 @@ import { import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { findTemplate, getEntityBaseUrl, getWorkingDirectory } from './helpers'; -import { - AuthorizeResult, - PermissionRuleParams, -} from '@backstage/plugin-permission-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { createConditionAuthorizer, createPermissionIntegrationRouter, @@ -420,12 +411,8 @@ export async function createRouter( permissions: scaffolderActionPermissions, rules: actionRules, }, - { - resourceType: 'basic', - permissions: scaffolderTaskPermissions, - rules: [], - }, ], + permissions: scaffolderTaskPermissions, }); router.use(permissionIntegrationRouter); @@ -464,20 +451,7 @@ export async function createRouter( }); }, ) - .get('/v2/actions', async (req, res) => { - const credentials = await httpAuth.credentials(req); - if (permissions) { - const authorizationResponse = ( - await permissions.authorizeConditional( - [{ permission: actionReadPermission }], - { credentials: credentials }, - ) - )[0]; - if (authorizationResponse.result === AuthorizeResult.DENY) { - throw new NotAllowedError(); - } - } - + .get('/v2/actions', async (_req, res) => { const actionsList = actionRegistry.list().map(action => { return { id: action.id, diff --git a/plugins/scaffolder-common/src/permissions.ts b/plugins/scaffolder-common/src/permissions.ts index 6bd7e130ec..c441b48d5d 100644 --- a/plugins/scaffolder-common/src/permissions.ts +++ b/plugins/scaffolder-common/src/permissions.ts @@ -42,19 +42,6 @@ export const actionExecutePermission = createPermission({ resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, }); -// TODO: Figure out whether to convert this to a basic permission or remove it completely since the current rules aren't applicable to this permission -/** - * This permission is used to authorize actions that involve access the action registry - * - * @alpha - */ -export const actionReadPermission = createPermission({ - name: 'scaffolder.action.read', - attributes: { - action: 'read', - }, - resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, -}); /** * This permission is used to authorize actions that involve reading * one or more parameters from a template. @@ -126,15 +113,6 @@ export const taskCancelPermission = createPermission({ attributes: {}, }); -/** - * List of all the scaffolder permissions - * @alpha - */ -export const scaffolderPermissions = [ - templateParameterReadPermission, - templateStepReadPermission, -]; - /** * List of the scaffolder permissions that are associated with template steps and parameters. * @alpha @@ -148,10 +126,7 @@ export const scaffolderTemplatePermissions = [ * List of the scaffolder permissions that are associated with scaffolder actions. * @alpha */ -export const scaffolderActionPermissions = [ - actionExecutePermission, - actionReadPermission, -]; +export const scaffolderActionPermissions = [actionExecutePermission]; /** * List of the scaffolder permissions that are associated with scaffolder tasks. @@ -162,3 +137,13 @@ export const scaffolderTaskPermissions = [ taskCreatePermission, taskReadPermission, ]; + +/** + * List of all the scaffolder permissions + * @alpha + */ +export const scaffolderPermissions = [ + ...scaffolderTemplatePermissions, + ...scaffolderActionPermissions, + ...scaffolderTaskPermissions, +]; From 18cd46471f2936027cd2492523892f27bbe4bf3b Mon Sep 17 00:00:00 2001 From: Matheus Castiglioni Date: Tue, 7 May 2024 15:01:16 -0300 Subject: [PATCH 295/567] chore(plugins/scaffolder-backend-module-github): making config optional Signed-off-by: Matheus Castiglioni --- .../api-report.md | 2 +- .../src/actions/githubPullRequest.test.ts | 60 +++++++++++++++++++ .../src/actions/githubPullRequest.ts | 8 +-- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index 4f3cd819ad..b1a1c80af5 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -128,7 +128,7 @@ export interface CreateGithubPullRequestActionOptions { } | null>; } >; - config: Config; + config?: Config; githubCredentialsProvider?: GithubCredentialsProvider; integrations: ScmIntegrationRegistry; } diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index dd7641ce4c..20508494b8 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -955,4 +955,64 @@ describe('createPublishGithubPullRequestAction', () => { }); }); }); + + describe('with author fallback and no config', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + gitAuthorName: 'Foo Bar', + }; + + mockDir.setContent({ + [workspacePath]: { 'file.txt': 'Hello there!' }, + }); + + ctx = createMockActionContext({ input, workspacePath }); + }); + + it('creates a pull request with using author name and email fallback when have no config', async () => { + const clientFactory = jest.fn(async () => fakeClient as any); + const githubCredentialsProvider: GithubCredentialsProvider = { + getCredentials: jest.fn(), + }; + + const instanceWithConfig = createPublishGithubPullRequestAction({ + integrations, + githubCredentialsProvider, + clientFactory, + }); + + await instanceWithConfig.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': { + content: Buffer.from('Hello there!').toString('base64'), + encoding: 'base64', + mode: '100644', + }, + }, + author: { + email: 'scaffolder@backstage.io', + name: 'Foo Bar', + }, + }, + ], + }); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index cc5e1280ef..a72ec8345b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -104,7 +104,7 @@ export interface CreateGithubPullRequestActionOptions { /** * An instance of {@link @backstage/config#Config} that will be used in the action. */ - config: Config; + config?: Config; } type GithubPullRequest = { @@ -351,7 +351,7 @@ export const createPublishGithubPullRequestAction = ( files, commit: commitMessage ?? - config.getOptionalString('scaffolder.defaultCommitMessage') ?? + config?.getOptionalString('scaffolder.defaultCommitMessage') ?? title, }, ], @@ -365,10 +365,10 @@ export const createPublishGithubPullRequestAction = ( const gitAuthorInfo = { name: gitAuthorName ?? - config.getOptionalString('scaffolder.defaultAuthor.name'), + config?.getOptionalString('scaffolder.defaultAuthor.name'), email: gitAuthorEmail ?? - config.getOptionalString('scaffolder.defaultAuthor.email'), + config?.getOptionalString('scaffolder.defaultAuthor.email'), }; if (gitAuthorInfo.name || gitAuthorInfo.email) { From a1735a9f112323453ccbd2aeda849f47ce84119f Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Tue, 7 May 2024 15:46:41 -0400 Subject: [PATCH 296/567] chore(scaffolder-backend): update api-report Signed-off-by: Frank Kong --- plugins/scaffolder-common/api-report-alpha.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-common/api-report-alpha.md b/plugins/scaffolder-common/api-report-alpha.md index a06f3123e7..36b6a7cdb0 100644 --- a/plugins/scaffolder-common/api-report-alpha.md +++ b/plugins/scaffolder-common/api-report-alpha.md @@ -9,9 +9,6 @@ import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha export const actionExecutePermission: ResourcePermission<'scaffolder-action'>; -// @alpha -export const actionReadPermission: ResourcePermission<'scaffolder-action'>; - // @alpha export const RESOURCE_TYPE_SCAFFOLDER_ACTION = 'scaffolder-action'; @@ -22,7 +19,11 @@ export const RESOURCE_TYPE_SCAFFOLDER_TEMPLATE = 'scaffolder-template'; export const scaffolderActionPermissions: ResourcePermission<'scaffolder-action'>[]; // @alpha -export const scaffolderPermissions: ResourcePermission<'scaffolder-template'>[]; +export const scaffolderPermissions: ( + | BasicPermission + | ResourcePermission<'scaffolder-action'> + | ResourcePermission<'scaffolder-template'> +)[]; // @alpha export const scaffolderTaskPermissions: BasicPermission[]; From 5252ee126829b9d1ccd9f054f976ad3f8a2adc2c Mon Sep 17 00:00:00 2001 From: Thomas Cardonne Date: Tue, 7 May 2024 23:03:35 +0200 Subject: [PATCH 297/567] fix(search-backend-module-elasticsearch): correctly resolve error handling promise Signed-off-by: Thomas Cardonne --- .changeset/afraid-ghosts-watch.md | 5 +++++ .../src/engines/ElasticSearchSearchEngine.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/afraid-ghosts-watch.md diff --git a/.changeset/afraid-ghosts-watch.md b/.changeset/afraid-ghosts-watch.md new file mode 100644 index 0000000000..b277baaff1 --- /dev/null +++ b/.changeset/afraid-ghosts-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fix never resolved indexer promise. diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index f8c996df51..93312c9fba 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -345,6 +345,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { attempts++; } + done(); }); if (cleanupError) { From 03045fc530d8c29cb753ef5b01bc29c2b3585255 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Mon, 6 May 2024 11:51:16 -0700 Subject: [PATCH 298/567] feat: first attempt at adding jwks-auth to external token handlers Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 32 ++++++++ .../auth/external/ExternalTokenHandler.ts | 3 + .../implementations/auth/external/jwks.ts | 73 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/jwks.ts diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index c1d6643769..b9cc6151af 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -81,6 +81,38 @@ header: Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW ``` +## JWKS Token Auth + +This access method allows for external caller token authentication using configured JWKS. +This is useful for callers that are authenticating to your instance of Backstage with +third-party tools, such as Auth0. + +You can configure this access method by adding one or more entries of type `jwks` +to the `backend.auth.externalAccess` app-config key: + +```yaml title="in e.g. app-config.production.yaml" +backend: + auth: + externalAccess: + - type: jwks + options: + uri: https://example.com/.well-known/jwks.json + issuers: + - https://example.com + algorithms: + - RS256 + audiences: + - example + - type: jwks + options: + uri: https://another-example.com/.well-known/jwks.json + issuers: + - https://example.com +``` + +The subject returned from the token verification will become part of the +credentials object that the request recipients get. + ## Legacy Tokens Plugins and backends that are _not_ on the new backend system use a legacy token diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts index 588a1f1794..79ad8dd3c4 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts @@ -21,6 +21,7 @@ import { import { LegacyTokenHandler } from './legacy'; import { StaticTokenHandler } from './static'; import { TokenHandler } from './types'; +import { JWKSHandler } from './jwks'; const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; const OLD_CONFIG_KEY = 'backend.auth.keys'; @@ -40,9 +41,11 @@ export class ExternalTokenHandler { const staticHandler = new StaticTokenHandler(); const legacyHandler = new LegacyTokenHandler(); + const jwksHandler = new JWKSHandler(); const handlers: Record = { static: staticHandler, legacy: legacyHandler, + jwks: jwksHandler, }; // Load the new-style handlers diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts new file mode 100644 index 0000000000..6ca7d010b2 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { jwtVerify, createRemoteJWKSet } from 'jose'; +import { Config } from '@backstage/config'; +import { TokenHandler } from './types'; + +/** + * Handles `type: jwks` access. + * + * @internal + */ +export class JWKSHandler implements TokenHandler { + #entries: Array<{ + algorithms: string[]; + audiences: string[]; + issuers: string[]; + uri: string; + }> = []; + + add(options: Config) { + const algorithms = options.getOptionalStringArray('algorithms') ?? []; + const issuers = options.getOptionalStringArray('issuers') ?? []; + const audiences = options.getOptionalStringArray('audiences') ?? []; + const uri = options.getString('uri'); + + if (!uri.match(/^\S+$/)) { + throw new Error('Illegal token, must be a set of non-space characters'); + } + + if (!issuers.every(issuer => issuer.match(/^\S+$/))) { + throw new Error('Illegal issuer, must be a set of non-space characters'); + } + + this.#entries.push({ algorithms, audiences, issuers, uri }); + } + + async verifyToken(token: string) { + // not sure if we would need to support multiple jwks entries, but implementing to match static/legacy token handlers + for (const entry of this.#entries) { + try { + const jwks = createRemoteJWKSet(new URL(entry.uri)); + const { + payload: { sub }, + } = await jwtVerify(token, jwks, { + algorithms: entry.algorithms, + issuer: entry.issuers, + audience: entry.audiences, + }); + + if (sub) { + return { subject: sub }; + } + } catch { + continue; + } + } + return undefined; + } +} From e978badcebaeab431f53180ce58f4f2ceefb5582 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 09:55:53 -0700 Subject: [PATCH 299/567] test: add unit tests for jwks access Signed-off-by: Ryan Hanchett --- .../auth/external/jwks.test.ts | 202 ++++++++++++++++++ .../implementations/auth/external/jwks.ts | 12 +- 2 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts new file mode 100644 index 0000000000..c4d9f37b23 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -0,0 +1,202 @@ +/* + * 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 { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { SignJWT, exportJWK, generateKeyPair } from 'jose'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { v4 as uuid } from 'uuid'; +import { JWKSHandler } from './jwks'; + +interface AnyJWK extends Record { + use: 'sig'; + alg: string; + kid: string; + kty: string; +} +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend +// Since this is re-used in several tests, I wonder if it should get refactored +// into @backstage/backend-test-utils +class FakeTokenFactory { + private readonly keys = new Array(); + + constructor( + private readonly options: { + issuer: string; + keyDurationSeconds: number; + }, + ) {} + + async issueToken(params: { + claims: { + sub: string; + ent?: string[]; + }; + }): Promise { + const pair = await generateKeyPair('RS256'); + const publicKey = await exportJWK(pair.publicKey); + const kid = uuid(); + publicKey.kid = kid; + this.keys.push(publicKey as AnyJWK); + + const iss = this.options.issuer; + const sub = params.claims.sub; + const ent = params.claims.ent; + const aud = 'backstage'; + const iat = Math.floor(Date.now() / 1000); + const exp = iat + this.options.keyDurationSeconds; + + return new SignJWT({ iss, sub, aud, iat, exp, ent, kid }) + .setProtectedHeader({ alg: 'RS256', ent: ent, kid: kid }) + .setIssuer(iss) + .setAudience(aud) + .setSubject(sub) + .setIssuedAt(iat) + .setExpirationTime(exp) + .sign(pair.privateKey); + } + + async listPublicKeys(): Promise<{ keys: AnyJWK[] }> { + return { keys: this.keys }; + } +} + +const server = setupServer(); +const mockBaseUrl = 'http://backstage:9191/i-am-a-mock-base'; + +describe('JWKSHandler', () => { + let factory: FakeTokenFactory; + let mockSubject: string; + const keyDurationSeconds = 5; + + setupRequestMockHandlers(server); + + beforeEach(() => { + mockSubject = 'test_subject'; + + factory = new FakeTokenFactory({ + issuer: mockBaseUrl, + keyDurationSeconds, + }); + + server.use( + rest.get(`${mockBaseUrl}/.well-known/jwks.json`, async (_, res, ctx) => { + const keys = await factory.listPublicKeys(); + return res(ctx.json(keys)); + }), + ); + }); + + it('verifies token with valid entry', async () => { + const validEntry = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: [mockBaseUrl], + audiences: ['backstage'], + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(validEntry)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toEqual({ subject: mockSubject }); + }); + + it('skips invalid entry and continues verification', async () => { + const invalidEntry = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: ['fakeIssuer'], + audiences: ['fakeAud'], + }; + + const validEntry = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: ['multiple-issuers', mockBaseUrl], + audiences: ['multiple-audiences', 'backstage'], + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(invalidEntry)); + jwksHandler.add(new ConfigReader(validEntry)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toEqual({ subject: mockSubject }); + }); + + it('returns undefined if no valid entry found', async () => { + const invalidEntry1 = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: [mockBaseUrl], + audiences: [], + }; + + const invalidEntry2 = { + uri: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['HS256'], + issuers: [], + audiences: ['backstage'], + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(invalidEntry1)); + jwksHandler.add(new ConfigReader(invalidEntry2)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toBeUndefined(); + }); + + it('rejects bad config', () => { + const jwksHandler = new JWKSHandler(); + + expect(() => { + jwksHandler.add( + new ConfigReader({ + uri: 'https://exampl e.com/jwks', + }), + ); + }).toThrow('Illegal URI, must be a set of non-space characters'); + expect(() => { + jwksHandler.add( + new ConfigReader({ + uri: 'https://example.com/jwks\n', + }), + ); + }).toThrow('Illegal URI, must be a set of non-space characters'); + }); + + it('gracefully handles no added tokens', async () => { + const handler = new JWKSHandler(); + await expect(handler.verifyToken('ghi')).resolves.toBeUndefined(); + }); +}); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 6ca7d010b2..34683647df 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -26,7 +26,7 @@ import { TokenHandler } from './types'; export class JWKSHandler implements TokenHandler { #entries: Array<{ algorithms: string[]; - audiences: string[]; + audiences: string[] | string; issuers: string[]; uri: string; }> = []; @@ -34,22 +34,18 @@ export class JWKSHandler implements TokenHandler { add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms') ?? []; const issuers = options.getOptionalStringArray('issuers') ?? []; - const audiences = options.getOptionalStringArray('audiences') ?? []; + // if audience is unset, an empty string is valid, but an empty array is not + const audiences = options.getOptionalStringArray('audiences') ?? ''; const uri = options.getString('uri'); if (!uri.match(/^\S+$/)) { - throw new Error('Illegal token, must be a set of non-space characters'); - } - - if (!issuers.every(issuer => issuer.match(/^\S+$/))) { - throw new Error('Illegal issuer, must be a set of non-space characters'); + throw new Error('Illegal URI, must be a set of non-space characters'); } this.#entries.push({ algorithms, audiences, issuers, uri }); } async verifyToken(token: string) { - // not sure if we would need to support multiple jwks entries, but implementing to match static/legacy token handlers for (const entry of this.#entries) { try { const jwks = createRemoteJWKSet(new URL(entry.uri)); From 23dff40aa2865a52c37f32faae270fa71e8700f8 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 10:59:31 -0700 Subject: [PATCH 300/567] docs: expand on jwks docs Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index b9cc6151af..9ecd39ba8f 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -83,9 +83,9 @@ Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW ## JWKS Token Auth -This access method allows for external caller token authentication using configured JWKS. -This is useful for callers that are authenticating to your instance of Backstage with -third-party tools, such as Auth0. +This access method allows for external caller token authentication using configured +JSON Web Key Sets (JWKS). This is useful for callers that are authenticating to our +instance of Backstage with third-party tools, such as Auth0. You can configure this access method by adding one or more entries of type `jwks` to the `backend.auth.externalAccess` app-config key: @@ -110,8 +110,22 @@ backend: - https://example.com ``` +The URI should point at an unauthenticated endpoint that returns the JWKS. + +Issuers specifies the issuer(s) of the JWT that the authenticating app will accept. +Passed JWTs must have an `iss` claim which matches one of the specified issuers. + +Algorithms specifies the algorithm(s) that are used to verify the JWT. The passed JWTs +must have been signed using one of the listed algorithms. + +Audiences speficies the intended audience(s) of the JWT. The passed JWTs must have an "aud" +claim that matches one of the audiences specified, or have no audience specified. + +For additional details regarding the JWKS configuration, please consult your authentication +provider's documentation. + The subject returned from the token verification will become part of the -credentials object that the request recipients get. +credentials object that the request recipient plugins get. ## Legacy Tokens From 398b82a3685bd0c623b5cf75063ba6d09b66ee9c Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 13:53:46 -0700 Subject: [PATCH 301/567] chore: add changeset Signed-off-by: Ryan Hanchett --- .changeset/famous-monkeys-count.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-monkeys-count.md diff --git a/.changeset/famous-monkeys-count.md b/.changeset/famous-monkeys-count.md new file mode 100644 index 0000000000..c5151b38c3 --- /dev/null +++ b/.changeset/famous-monkeys-count.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Add support for JWKS tokens in ExternalTokenHandler. From 9b0db3f495e04a8381c91bd1e7d5f3168d1067cc Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 7 May 2024 14:08:45 -0700 Subject: [PATCH 302/567] chore: clean up comments before opening PR Signed-off-by: Ryan Hanchett --- .../src/services/implementations/auth/external/jwks.test.ts | 2 -- .../src/services/implementations/auth/external/jwks.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index c4d9f37b23..95df6f56ff 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -28,8 +28,6 @@ interface AnyJWK extends Record { kty: string; } // Simplified copy of TokenFactory in @backstage/plugin-auth-backend -// Since this is re-used in several tests, I wonder if it should get refactored -// into @backstage/backend-test-utils class FakeTokenFactory { private readonly keys = new Array(); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 34683647df..5c3738504d 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -34,7 +34,6 @@ export class JWKSHandler implements TokenHandler { add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms') ?? []; const issuers = options.getOptionalStringArray('issuers') ?? []; - // if audience is unset, an empty string is valid, but an empty array is not const audiences = options.getOptionalStringArray('audiences') ?? ''; const uri = options.getString('uri'); From 8d722ae1f3360dd386bb66f91f6844f1b85680ec Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 8 May 2024 10:28:35 +0200 Subject: [PATCH 303/567] backend-common: add test for legacy plugin auth Signed-off-by: Patrik Oldsberg --- packages/backend-common/src/legacy.test.ts | 118 +++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 packages/backend-common/src/legacy.test.ts diff --git a/packages/backend-common/src/legacy.test.ts b/packages/backend-common/src/legacy.test.ts new file mode 100644 index 0000000000..1ddebce5c4 --- /dev/null +++ b/packages/backend-common/src/legacy.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { EventEmitter } from 'events'; +import { Router } from 'express'; +import { createLegacyAuthAdapters } from './auth'; +import { legacyPlugin } from './legacy'; +import { + authServiceFactory, + tokenManagerServiceFactory, +} from '@backstage/backend-app-api'; + +describe('legacyPlugin', () => { + it('can auth across the new and old systems', async () => { + const emitter = new EventEmitter(); + + const done = new Promise(resolve => { + emitter.once('done', () => { + emitter.once('done', resolve); + }); + }); + + await startTestBackend({ + features: [ + authServiceFactory, + tokenManagerServiceFactory, + mockServices.rootConfig.factory({ + data: { + backend: { + auth: { + keys: [ + { + secret: 'test', + }, + ], + }, + }, + }, + }), + createBackendPlugin({ + pluginId: 'new', + register(reg) { + reg.registerInit({ + deps: { + auth: coreServices.auth, + discovery: coreServices.discovery, + }, + async init({ auth }) { + emitter.once('legacy-token', async otherToken => { + const credentials = await auth.authenticate(otherToken); + expect(credentials.principal).toEqual({ + type: 'service', + subject: 'external:backstage-plugin', + }); + emitter.emit('done'); + }); + + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'old', + }); + emitter.emit('new-token', token); + }, + }); + }, + }), + legacyPlugin( + 'old', + Promise.resolve({ + async default({ tokenManager, identity, discovery }) { + const { auth } = createLegacyAuthAdapters({ + tokenManager, + identity, + discovery, + auth: undefined as any as typeof coreServices.auth.T, + httpAuth: undefined as any as typeof coreServices.httpAuth.T, + }); + + emitter.once('new-token', async otherToken => { + const credentials = await auth.authenticate(otherToken); + expect(credentials.principal).toEqual({ + type: 'service', + subject: 'external:backstage-plugin', + }); + emitter.emit('done'); + }); + + const { token } = await tokenManager.getToken(); + emitter.emit('legacy-token', token); + + return Router(); + }, + }), + ), + ], + }); + + await done; + }); +}); From fad761f52b92e2fd3731de783f5e239eb15e2491 Mon Sep 17 00:00:00 2001 From: AmbrishRamachandiran Date: Wed, 8 May 2024 15:34:19 +0530 Subject: [PATCH 304/567] Updated the document of permission check Signed-off-by: AmbrishRamachandiran --- .../plugin-authors/02-adding-a-basic-permission-check.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index f3380bdb6f..8541d64465 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -37,7 +37,11 @@ export const todoListPermissions = [todoListCreatePermission]; For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`). -> Note: We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/local-dev/cli-build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies. +:::note Note + +We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/local-dev/cli-build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies. + +::: ## Authorizing using the new permission From 989b57e9455e5c60844d03377a82e5a6763e6c3c Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Wed, 8 May 2024 13:11:32 +0200 Subject: [PATCH 305/567] cleaning up docs Signed-off-by: Peter Macdonald --- docs/permissions/getting-started.md | 33 ++--------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index 825fefe598..03290bd45e 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -22,38 +22,9 @@ Like many other parts of Backstage, the permissions framework relies on informat [The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. -## Integrating the permission framework with your Backstage instance +## Enable and test the permissions system -### 1. Set up the permission backend - -The permissions framework uses the `permission-backend` plugin to accept authorization requests from other plugins across your Backstage deployment. The default `@backstage/create-app` template includes the permission backend, but if you need to make the change manually, these are the steps: - -1. Add `@backstage/plugin-permission-backend` and `@backstage/plugin-permission-backend-module-allow-all-policy` to your backend dependencies, this will add the permission backend and a policy that allows all permissions: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/plugin-permission-backend @backstage/plugin-permission-backend-module-allow-all-policy -``` - -2. Add the following to `packages/backend/src/index.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. - -```typescript title="packages/backend/src/index.ts" -import { createBackend } from '@backstage/backend-defaults'; -const backend = createBackend(); -// ... -/* highlight-add-next-line */ -backend.add(import('@backstage/plugin-permission-backend/alpha')); -/* highlight-add-next-line */ -backend.add( - import('@backstage/plugin-permission-backend-module-allow-all-policy'), -); -// ... -backend.start(); -``` - -### 2. Enable and test the permissions system - -Now that the permission backend is running, it’s time to enable the permissions framework and make sure it’s working properly. +All you need to do now is enable the permissions system in your Backstage instance! 1. Set the property `permission.enabled` to `true` in `app-config.yaml`. From f57e0314c835cd89b1d2d6a727242ff93ba4db89 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Wed, 8 May 2024 09:07:30 -0300 Subject: [PATCH 306/567] chore: render icons from EntityDisplayName Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../components/EntityListComponent/EntityListComponent.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx index 58b541795d..12c7376f9a 100644 --- a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx +++ b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx @@ -149,11 +149,8 @@ export const EntityListComponent = (props: EntityListComponentProps) => { } : {})} > - {Icon && } - } + primary={} /> ); From f0b2a070844edf48c63ac8cf4c4209e02a2ecfc2 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Wed, 8 May 2024 09:32:38 -0300 Subject: [PATCH 307/567] chore: lint error resolved Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../components/EntityListComponent/EntityListComponent.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx index 12c7376f9a..80a2d90fcc 100644 --- a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx +++ b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx @@ -19,7 +19,7 @@ import { CompoundEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; -import { useApi, useApp } from '@backstage/core-plugin-api'; +import { useApi } from '@backstage/core-plugin-api'; import { EntityDisplayName, EntityRefLink, @@ -75,7 +75,6 @@ export const EntityListComponent = (props: EntityListComponentProps) => { withLinks = false, } = props; - const app = useApp(); const classes = useStyles(); const entityPresentationApi = useApi(entityPresentationApiRef); const [expandedUrls, setExpandedUrls] = useState([]); @@ -134,9 +133,6 @@ export const EntityListComponent = (props: EntityListComponentProps) => { > {sortEntities(r.entities).map(entity => { - const Icon = app.getSystemIcon( - `kind:${entity.kind.toLocaleLowerCase('en-US')}`, - ); return ( Date: Wed, 8 May 2024 14:52:10 +0200 Subject: [PATCH 308/567] Remove manual yarn postpack invocation Signed-off-by: Eric Peterson --- .changeset/cli-postpack-schmostschmack.md | 5 +++++ packages/cli/src/lib/packager/createDistWorkspace.ts | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .changeset/cli-postpack-schmostschmack.md diff --git a/.changeset/cli-postpack-schmostschmack.md b/.changeset/cli-postpack-schmostschmack.md new file mode 100644 index 0000000000..2500c89a8c --- /dev/null +++ b/.changeset/cli-postpack-schmostschmack.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `build-workspace` command no longer manually runs `yarn postpack`, relying instead on the fact that running `yarn pack` will automatically invoke the `postpack` script. No action is necessary if you are running the latest version of yarn 1, 3, or 4. diff --git a/packages/cli/src/lib/packager/createDistWorkspace.ts b/packages/cli/src/lib/packager/createDistWorkspace.ts index 7d4c9d4c6d..b966e23a85 100644 --- a/packages/cli/src/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/lib/packager/createDistWorkspace.ts @@ -309,10 +309,6 @@ async function moveToDistWorkspace( await run('yarn', ['pack', '--filename', archivePath], { cwd: target.dir, }); - // TODO(Rugvip): yarn pack doesn't call postpack, once the bug is fixed this can be removed - if (target.packageJson?.scripts?.postpack) { - await run('yarn', ['postpack'], { cwd: target.dir }); - } const outputDir = relativePath(paths.targetRoot, target.dir); const absoluteOutputPath = resolvePath(workspaceDir, outputDir); From a12639fa7fcb947470dbde2a3f314c96c297b9ca Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 8 May 2024 15:19:25 +0200 Subject: [PATCH 309/567] Update v1.27.0-next.2-changelog.md Signed-off-by: Patrik Oldsberg --- docs/releases/v1.27.0-next.2-changelog.md | 1536 --------------------- 1 file changed, 1536 deletions(-) diff --git a/docs/releases/v1.27.0-next.2-changelog.md b/docs/releases/v1.27.0-next.2-changelog.md index ac8fb0534f..3c2e688e3b 100644 --- a/docs/releases/v1.27.0-next.2-changelog.md +++ b/docs/releases/v1.27.0-next.2-changelog.md @@ -4,10 +4,6 @@ Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.27.0-next.2](h ## @backstage/backend-app-api@0.7.3-next.1 -# @backstage/backend-app-api - -## 0.7.2-next.1 - ### Patch Changes - 09f8988: Remove explicit `alg` check for user tokens in `verifyToken` @@ -20,1538 +16,6 @@ Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.27.0-next.2](h - @backstage/config-loader@1.8.0 - @backstage/backend-plugin-api@0.6.18-next.1 -## 0.7.1-next.0 - -### Patch Changes - -- 4cd5ff0: Add ability to configure the Node.js HTTP Server when configuring the root HTTP Router service -- e8199b1: Move the JWKS registration outside of the lifecycle middleware -- dc8c5dd: The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests. -- 025641b: Redact `meta` fields too with the logger -- 5863e02: Internal refactor to only create one external token handler -- Updated dependencies - - @backstage/plugin-auth-node@0.4.13-next.0 - - @backstage/backend-common@0.21.8-next.0 - - @backstage/backend-plugin-api@0.6.18-next.0 - - @backstage/backend-tasks@0.5.23-next.0 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.5 - - @backstage/config@1.2.0 - - @backstage/config-loader@1.8.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - - @backstage/plugin-permission-node@0.7.29-next.0 - -## 0.7.0 - -### Minor Changes - -- 3256f14: **BREAKING**: Modules are no longer loaded unless the plugin that they extend is present. - -### Patch Changes - -- 10327fb: Deprecate the `getPath` option for the `httpRouterServiceFactory` and more generally the ability to configure plugin API paths to be anything else than `/api/:pluginId/`. Requests towards `/api/*` that do not match an installed plugin will also no longer be handled by the index router, typically instead returning a 404. - -- 2c50516: Fix auth cookie issuance for split backend deployments by preferring to set it against the request target host instead of origin - -- 7e584d6: Fixed a bug where expired cookies would not be refreshed. - -- 1a20b12: Make the auth service create and validate dedicated OBO tokens, containing the user identity proof. - -- 00fca28: Implemented support for external access using both the legacy token form and static tokens. - -- d5a1fe1: Replaced winston logger with `LoggerService` - -- bce0879: Service-to-service authentication has been improved. - - Each plugin now has the capability to generate its own signing keys for token issuance. The generated public keys are stored in a database, and they are made accessible through a newly created endpoint: `/.backstage/auth/v1/jwks.json`. - - `AuthService` can now issue tokens with a reduced scope using the `getPluginRequestToken` method. This improvement enables plugins to identify the plugin originating the request. - -- 54f2ac8: Added `initialization` option to `createServiceFactory` which defines the initialization strategy for the service. The default strategy mimics the current behavior where plugin scoped services are initialized lazily by default and root scoped services are initialized eagerly. - -- 56f81b5: Improved error message thrown by `AuthService` when requesting a token for plugins that don't support the new authentication tokens. - -- 25ea3d2: Minor internal restructuring - -- d62bc51: Add support for limited user tokens by using user identity proof provided by the auth backend. - -- c884b9a: Automatically creates a get and delete cookie endpoint when a `user-cookie` policy is added. - -- Updated dependencies - - @backstage/backend-common@0.21.7 - - @backstage/config-loader@1.8.0 - - @backstage/plugin-permission-node@0.7.28 - - @backstage/backend-plugin-api@0.6.17 - - @backstage/backend-tasks@0.5.22 - - @backstage/plugin-auth-node@0.4.12 - - @backstage/cli-node@0.2.5 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.7.0-next.1 - -### Minor Changes - -- 3256f14: **BREAKING**: Modules are no longer loaded unless the plugin that they extend is present. - -### Patch Changes - -- 10327fb: Deprecate the `getPath` option for the `httpRouterServiceFactory` and more generally the ability to configure plugin API paths to be anything else than `/api/:pluginId/`. Requests towards `/api/*` that do not match an installed plugin will also no longer be handled by the index router, typically instead returning a 404. - -- 1a20b12: Make the auth service create and validate dedicated OBO tokens, containing the user identity proof. - -- bce0879: Service-to-service authentication has been improved. - - Each plugin now has the capability to generate its own signing keys for token issuance. The generated public keys are stored in a database, and they are made accessible through a newly created endpoint: `/.backstage/auth/v1/jwks.json`. - - `AuthService` can now issue tokens with a reduced scope using the `getPluginRequestToken` method. This improvement enables plugins to identify the plugin originating the request. - -- 54f2ac8: Added `initialization` option to `createServiceFactory` which defines the initialization strategy for the service. The default strategy mimics the current behavior where plugin scoped services are initialized lazily by default and root scoped services are initialized eagerly. - -- d62bc51: Add support for limited user tokens by using user identity proof provided by the auth backend. - -- c884b9a: Automatically creates a get and delete cookie endpoint when a `user-cookie` policy is added. - -- Updated dependencies - - @backstage/backend-common@0.21.7-next.1 - - @backstage/backend-plugin-api@0.6.17-next.1 - - @backstage/plugin-auth-node@0.4.12-next.1 - - @backstage/backend-tasks@0.5.22-next.1 - - @backstage/plugin-permission-node@0.7.28-next.1 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.4 - - @backstage/config@1.2.0 - - @backstage/config-loader@1.8.0-next.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.6.3-next.0 - -### Patch Changes - -- 7e584d6: Fixed a bug where expired cookies would not be refreshed. -- Updated dependencies - - @backstage/backend-common@0.21.7-next.0 - - @backstage/config-loader@1.8.0-next.0 - - @backstage/backend-plugin-api@0.6.17-next.0 - - @backstage/backend-tasks@0.5.22-next.0 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.4 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - - @backstage/plugin-auth-node@0.4.12-next.0 - - @backstage/plugin-permission-node@0.7.28-next.0 - -## 0.6.2 - -### Patch Changes - -- e848644: Temporarily revert the rate limiting -- Updated dependencies - - @backstage/plugin-auth-node@0.4.11 - - @backstage/backend-common@0.21.6 - - @backstage/backend-plugin-api@0.6.16 - - @backstage/plugin-permission-node@0.7.27 - - @backstage/backend-tasks@0.5.21 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.4 - - @backstage/config@1.2.0 - - @backstage/config-loader@1.7.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.6.1 - -### Patch Changes - -- de1f45d: Temporarily revert the rate limiting -- Updated dependencies - - @backstage/backend-common@0.21.5 - - @backstage/plugin-auth-node@0.4.10 - - @backstage/backend-tasks@0.5.20 - - @backstage/plugin-permission-node@0.7.26 - - @backstage/backend-plugin-api@0.6.15 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.4 - - @backstage/config@1.2.0 - - @backstage/config-loader@1.7.0 - - @backstage/errors@1.2.4 - - @backstage/types@1.1.1 - -## 0.6.0 - -### Minor Changes - -- 4a3d434: **BREAKING**: For users that have migrated to the new backend system, incoming requests will now be rejected if they are not properly authenticated (e.g. with a Backstage bearer token or a backend token). Please see the [Auth Service Migration tutorial](https://backstage.io/docs/tutorials/auth-service-migration) for more information on how to circumvent this behavior in the short term and how to properly leverage it in the longer term. - - Added service factories for the new [`auth`](https://backstage.io/docs/backend-system/core-services/auth/), [`httpAuth`](https://backstage.io/docs/backend-system/core-services/http-auth), and [`userInfo`](https://backstage.io/docs/backend-system/core-services/user-info) services that were created as part of [BEP-0003](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution). - -### Patch Changes - -- 999224f: Bump dependency `minimatch` to v9 -- 81e0120: Fixed an issue where configuration schema for the purpose of redacting secrets from logs was not being read correctly. -- 15fda44: Provide some sane defaults for `WinstonLogger.create` making some of the arguments optional -- 0502d82: Updated the `permissionsServiceFactory` to forward the `AuthService` to the implementation. -- 9d91128: Add the possibility to disable watching files in the new backend system -- a5d341e: Adds an initial rate-limiting implementation so that any incoming requests that have a `'none'` principal are rate-limited automatically. -- 9802004: Made the `DefaultUserInfoService` claims check stricter -- f235ca7: Make sure to not filter out schemas in `createConfigSecretEnumerator` -- af5f7a6: The experimental feature discovery service exported at the `/alpha` sub-path will no longer attempt to load packages that are not Backstage backend packages. -- Updated dependencies - - @backstage/backend-common@0.21.4 - - @backstage/plugin-auth-node@0.4.9 - - @backstage/config@1.2.0 - - @backstage/errors@1.2.4 - - @backstage/backend-plugin-api@0.6.14 - - @backstage/config-loader@1.7.0 - - @backstage/backend-tasks@0.5.19 - - @backstage/plugin-permission-node@0.7.25 - - @backstage/cli-node@0.2.4 - - @backstage/cli-common@0.1.13 - - @backstage/types@1.1.1 - -## 0.6.0-next.2 - -### Patch Changes - -- 15fda44: Provide some sane defaults for `WinstonLogger.create` making some of the arguments optional -- 9d91128: Add the possibility to disable watching files in the new backend system -- Updated dependencies - - @backstage/backend-common@0.21.4-next.2 - - @backstage/plugin-auth-node@0.4.9-next.2 - - @backstage/backend-plugin-api@0.6.14-next.2 - - @backstage/backend-tasks@0.5.19-next.2 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.4-next.0 - - @backstage/config@1.2.0-next.1 - - @backstage/config-loader@1.7.0-next.1 - - @backstage/errors@1.2.4-next.0 - - @backstage/types@1.1.1 - - @backstage/plugin-permission-node@0.7.25-next.2 - -## 0.6.0-next.1 - -### Patch Changes - -- 81e0120: Fixed an issue where configuration schema for the purpose of redacting secrets from logs was not being read correctly. -- f235ca7: Make sure to not filter out schemas in `createConfigSecretEnumerator` -- Updated dependencies - - @backstage/config@1.2.0-next.1 - - @backstage/config-loader@1.7.0-next.1 - - @backstage/backend-common@0.21.4-next.1 - - @backstage/backend-plugin-api@0.6.14-next.1 - - @backstage/backend-tasks@0.5.19-next.1 - - @backstage/plugin-auth-node@0.4.9-next.1 - - @backstage/plugin-permission-node@0.7.25-next.1 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.4-next.0 - - @backstage/errors@1.2.4-next.0 - - @backstage/types@1.1.1 - -## 0.6.0-next.0 - -### Minor Changes - -- 4a3d434: **BREAKING**: For users that have migrated to the new backend system, incoming requests will now be rejected if they are not properly authenticated (e.g. with a Backstage bearer token or a backend token). Please see the [Auth Service Migration tutorial](https://backstage.io/docs/tutorials/auth-service-migration) for more information on how to circumvent this behavior in the short term and how to properly leverage it in the longer term. - - Added service factories for the new [`auth`](https://backstage.io/docs/backend-system/core-services/auth/), [`httpAuth`](https://backstage.io/docs/backend-system/core-services/http-auth), and [`userInfo`](https://backstage.io/docs/backend-system/core-services/user-info) services that were created as part of [BEP-0003](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution). - -### Patch Changes - -- 999224f: Bump dependency `minimatch` to v9 -- 0502d82: Updated the `permissionsServiceFactory` to forward the `AuthService` to the implementation. -- 9802004: Made the `DefaultUserInfoService` claims check stricter -- Updated dependencies - - @backstage/backend-common@0.21.3-next.0 - - @backstage/plugin-auth-node@0.4.8-next.0 - - @backstage/errors@1.2.4-next.0 - - @backstage/backend-plugin-api@0.6.13-next.0 - - @backstage/backend-tasks@0.5.18-next.0 - - @backstage/plugin-permission-node@0.7.24-next.0 - - @backstage/cli-node@0.2.4-next.0 - - @backstage/config-loader@1.6.3-next.0 - - @backstage/config@1.1.2-next.0 - - @backstage/cli-common@0.1.13 - - @backstage/types@1.1.1 - -## 0.5.11 - -### Patch Changes - -- e0c18ef: Include the extension point ID and the module ID in the backend init error message. -- 7ae5704: Updated the default error handling middleware to filter out certain known error types that should never be returned in responses. The errors are instead logged along with a correlation ID, which is also returned in the response. Initially only PostgreSQL protocol errors from the `pg-protocol` package are filtered out. -- 9aac2b0: Use `--cwd` as the first `yarn` argument -- 54ad8e1: Allow the `createConfigSecretEnumerator` to take an optional `schema` argument with an already-loaded global configuration schema. -- 6bb6f3e: Updated dependency `fs-extra` to `^11.2.0`. - Updated dependency `@types/fs-extra` to `^11.0.0`. -- Updated dependencies - - @backstage/backend-common@0.21.0 - - @backstage/plugin-auth-node@0.4.4 - - @backstage/cli-node@0.2.3 - - @backstage/backend-plugin-api@0.6.10 - - @backstage/backend-tasks@0.5.15 - - @backstage/config-loader@1.6.2 - - @backstage/plugin-permission-node@0.7.21 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.11-next.3 - -### Patch Changes - -- 54ad8e1: Allow the `createConfigSecretEnumerator` to take an optional `schema` argument with an already-loaded global configuration schema. -- Updated dependencies - - @backstage/backend-common@0.21.0-next.3 - - @backstage/cli-node@0.2.3-next.0 - - @backstage/backend-tasks@0.5.15-next.3 - - @backstage/config-loader@1.6.2-next.0 - - @backstage/plugin-auth-node@0.4.4-next.3 - - @backstage/plugin-permission-node@0.7.21-next.3 - - @backstage/backend-plugin-api@0.6.10-next.3 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.11-next.2 - -### Patch Changes - -- 9aac2b0: Use `--cwd` as the first `yarn` argument -- Updated dependencies - - @backstage/backend-common@0.21.0-next.2 - - @backstage/backend-plugin-api@0.6.10-next.2 - - @backstage/backend-tasks@0.5.15-next.2 - - @backstage/plugin-auth-node@0.4.4-next.2 - - @backstage/plugin-permission-node@0.7.21-next.2 - - @backstage/config@1.1.1 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.2 - - @backstage/config-loader@1.6.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.11-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.10-next.1 - - @backstage/backend-common@0.21.0-next.1 - - @backstage/backend-tasks@0.5.15-next.1 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.2 - - @backstage/config@1.1.1 - - @backstage/config-loader@1.6.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - - @backstage/plugin-auth-node@0.4.4-next.1 - - @backstage/plugin-permission-node@0.7.21-next.1 - -## 0.5.11-next.0 - -### Patch Changes - -- e0c18ef: Include the extension point ID and the module ID in the backend init error message. -- Updated dependencies - - @backstage/backend-common@0.21.0-next.0 - - @backstage/backend-tasks@0.5.15-next.0 - - @backstage/cli-node@0.2.2 - - @backstage/config-loader@1.6.1 - - @backstage/plugin-auth-node@0.4.4-next.0 - - @backstage/plugin-permission-node@0.7.21-next.0 - - @backstage/backend-plugin-api@0.6.10-next.0 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.10 - -### Patch Changes - -- 516fd3e: Updated README to reflect release status -- Updated dependencies - - @backstage/backend-common@0.20.1 - - @backstage/config-loader@1.6.1 - - @backstage/cli-node@0.2.2 - - @backstage/backend-plugin-api@0.6.9 - - @backstage/plugin-permission-node@0.7.20 - - @backstage/backend-tasks@0.5.14 - - @backstage/plugin-auth-node@0.4.3 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.10-next.2 - -### Patch Changes - -- 516fd3e: Updated README to reflect release status -- Updated dependencies - - @backstage/backend-plugin-api@0.6.9-next.2 - - @backstage/backend-common@0.20.1-next.2 - - @backstage/plugin-auth-node@0.4.3-next.2 - - @backstage/plugin-permission-node@0.7.20-next.2 - - @backstage/backend-tasks@0.5.14-next.2 - - @backstage/cli-node@0.2.2-next.0 - - @backstage/config-loader@1.6.1-next.0 - -## 0.5.10-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/config-loader@1.6.1-next.0 - - @backstage/cli-node@0.2.2-next.0 - - @backstage/backend-common@0.20.1-next.1 - - @backstage/config@1.1.1 - - @backstage/backend-tasks@0.5.14-next.1 - - @backstage/plugin-auth-node@0.4.3-next.1 - - @backstage/plugin-permission-node@0.7.20-next.1 - - @backstage/backend-plugin-api@0.6.9-next.1 - - @backstage/cli-common@0.1.13 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.10-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.1-next.0 - - @backstage/backend-plugin-api@0.6.9-next.0 - - @backstage/backend-tasks@0.5.14-next.0 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.1 - - @backstage/config@1.1.1 - - @backstage/config-loader@1.6.0 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - - @backstage/plugin-auth-node@0.4.3-next.0 - - @backstage/plugin-permission-node@0.7.20-next.0 - -## 0.5.9 - -### Patch Changes - -- 1da5f43: Ensure redaction of secrets that have accidental extra whitespace around them -- 9f8f266: Add redacting for secrets in stack traces of logs -- Updated dependencies - - @backstage/backend-common@0.20.0 - - @backstage/config-loader@1.6.0 - - @backstage/backend-tasks@0.5.13 - - @backstage/plugin-auth-node@0.4.2 - - @backstage/plugin-permission-node@0.7.19 - - @backstage/cli-node@0.2.1 - - @backstage/backend-plugin-api@0.6.8 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.9-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.0-next.3 - - @backstage/backend-plugin-api@0.6.8-next.3 - - @backstage/backend-tasks@0.5.13-next.3 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.0 - - @backstage/config@1.1.1 - - @backstage/config-loader@1.6.0-next.0 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - - @backstage/plugin-auth-node@0.4.2-next.3 - - @backstage/plugin-permission-node@0.7.19-next.3 - -## 0.5.9-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/config-loader@1.6.0-next.0 - - @backstage/backend-common@0.20.0-next.2 - - @backstage/plugin-auth-node@0.4.2-next.2 - - @backstage/backend-plugin-api@0.6.8-next.2 - - @backstage/backend-tasks@0.5.13-next.2 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - - @backstage/plugin-permission-node@0.7.19-next.2 - -## 0.5.9-next.1 - -### Patch Changes - -- 1da5f434f3: Ensure redaction of secrets that have accidental extra whitespace around them -- 9f8f266ff4: Add redacting for secrets in stack traces of logs -- Updated dependencies - - @backstage/backend-common@0.20.0-next.1 - - @backstage/backend-plugin-api@0.6.8-next.1 - - @backstage/backend-tasks@0.5.13-next.1 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.0 - - @backstage/config@1.1.1 - - @backstage/config-loader@1.5.3 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - - @backstage/plugin-auth-node@0.4.2-next.1 - - @backstage/plugin-permission-node@0.7.19-next.1 - -## 0.5.9-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.20.0-next.0 - - @backstage/backend-tasks@0.5.13-next.0 - - @backstage/plugin-auth-node@0.4.2-next.0 - - @backstage/plugin-permission-node@0.7.19-next.0 - - @backstage/backend-plugin-api@0.6.8-next.0 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.0 - - @backstage/config@1.1.1 - - @backstage/config-loader@1.5.3 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.8 - -### Patch Changes - -- bc9a18d5ec: Added a workaround for double `default` wrapping when dynamically importing CommonJS modules with default exports. -- Updated dependencies - - @backstage/config-loader@1.5.3 - - @backstage/cli-node@0.2.0 - - @backstage/backend-common@0.19.9 - - @backstage/backend-plugin-api@0.6.7 - - @backstage/backend-tasks@0.5.12 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - - @backstage/plugin-auth-node@0.4.1 - - @backstage/plugin-permission-node@0.7.18 - -## 0.5.8-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.6.7-next.2 - - @backstage/backend-common@0.19.9-next.2 - - @backstage/backend-tasks@0.5.12-next.2 - - @backstage/plugin-auth-node@0.4.1-next.2 - - @backstage/plugin-permission-node@0.7.18-next.2 - - @backstage/config-loader@1.5.3-next.0 - -## 0.5.8-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.9-next.1 - - @backstage/backend-tasks@0.5.12-next.1 - - @backstage/config-loader@1.5.3-next.0 - - @backstage/plugin-auth-node@0.4.1-next.1 - - @backstage/plugin-permission-node@0.7.18-next.1 - - @backstage/backend-plugin-api@0.6.7-next.1 - - @backstage/cli-common@0.1.13 - - @backstage/cli-node@0.2.0-next.0 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - -## 0.5.8-next.0 - -### Patch Changes - -- bc9a18d5ec: Added a workaround for double `default` wrapping when dynamically importing CommonJS modules with default exports. -- Updated dependencies - - @backstage/config-loader@1.5.2-next.0 - - @backstage/cli-node@0.2.0-next.0 - - @backstage/backend-common@0.19.9-next.0 - - @backstage/backend-plugin-api@0.6.7-next.0 - - @backstage/backend-tasks@0.5.12-next.0 - - @backstage/cli-common@0.1.13 - - @backstage/config@1.1.1 - - @backstage/errors@1.2.3 - - @backstage/types@1.1.1 - - @backstage/plugin-auth-node@0.4.1-next.0 - - @backstage/plugin-permission-node@0.7.18-next.0 - -## 0.5.6 - -### Patch Changes - -- 74491c9602: Moved `HostDiscovery` from `@backstage/backend-common`. -- a4617c422a: Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`. -- Updated dependencies - - @backstage/backend-tasks@0.5.11 - - @backstage/backend-common@0.19.8 - - @backstage/plugin-auth-node@0.4.0 - - @backstage/config-loader@1.5.1 - - @backstage/errors@1.2.3 - - @backstage/cli-common@0.1.13 - - @backstage/backend-plugin-api@0.6.6 - - @backstage/plugin-permission-node@0.7.17 - - @backstage/cli-node@0.1.5 - - @backstage/config@1.1.1 - - @backstage/types@1.1.1 - -## 0.5.6-next.2 - -### Patch Changes - -- 74491c9602: Moved `HostDiscovery` from `@backstage/backend-common`. -- a4617c422a: Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`. -- Updated dependencies - - @backstage/backend-common@0.19.8-next.2 - - @backstage/plugin-auth-node@0.4.0-next.2 - - @backstage/config-loader@1.5.1-next.1 - - @backstage/errors@1.2.3-next.0 - - @backstage/backend-tasks@0.5.11-next.2 - - @backstage/plugin-permission-node@0.7.17-next.2 - - @backstage/backend-plugin-api@0.6.6-next.2 - - @backstage/cli-common@0.1.13-next.0 - - @backstage/cli-node@0.1.5-next.1 - - @backstage/config@1.1.1-next.0 - - @backstage/types@1.1.1 - -## 0.5.5-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-tasks@0.5.10-next.1 - - @backstage/backend-common@0.19.7-next.1 - - @backstage/backend-plugin-api@0.6.5-next.1 - - @backstage/plugin-auth-node@0.3.2-next.1 - - @backstage/plugin-permission-node@0.7.16-next.1 - - @backstage/config@1.1.0 - - @backstage/cli-common@0.1.13-next.0 - - @backstage/cli-node@0.1.5-next.0 - - @backstage/config-loader@1.5.1-next.0 - - @backstage/errors@1.2.2 - - @backstage/types@1.1.1 - -## 0.5.5-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-node@0.3.2-next.0 - - @backstage/config-loader@1.5.1-next.0 - - @backstage/cli-common@0.1.13-next.0 - - @backstage/backend-common@0.19.7-next.0 - - @backstage/config@1.1.0 - - @backstage/backend-plugin-api@0.6.5-next.0 - - @backstage/backend-tasks@0.5.10-next.0 - - @backstage/cli-node@0.1.5-next.0 - - @backstage/errors@1.2.2 - - @backstage/types@1.1.1 - - @backstage/plugin-permission-node@0.7.16-next.0 - -## 0.5.3 - -### Patch Changes - -- 154632d8753b: Add support for discovering additional service factories during startup. -- 37a20c7f14aa: Adds include and exclude configuration to feature discovery of backend packages - Adds alpha modules to feature discovery -- cb7fc410ed99: The experimental backend feature discovery now only considers default exports from packages. It no longer filters packages to include based on the package role, except that `'cli'` packages are ignored. However, the `"backstage"` field is still required in `package.json`. -- 3fc64b9e2f8f: Extension points are now tracked via their ID rather than reference, in order to support package duplication. -- 3b30b179cb38: Add support for installing features as package imports, for example `backend.add(import('my-plugin'))`. -- b219d097b3f4: Backend startup will now fail if any circular service dependencies are detected. -- Updated dependencies - - @backstage/backend-tasks@0.5.8 - - @backstage/backend-common@0.19.5 - - @backstage/plugin-auth-node@0.3.0 - - @backstage/config@1.1.0 - - @backstage/errors@1.2.2 - - @backstage/types@1.1.1 - - @backstage/plugin-permission-node@0.7.14 - - @backstage/backend-plugin-api@0.6.3 - - @backstage/config-loader@1.5.0 - - @backstage/cli-common@0.1.12 - - @backstage/cli-node@0.1.4 - -## 0.5.3-next.3 - -### Patch Changes - -- 154632d8753b: Add support for discovering additional service factories during startup. -- cb7fc410ed99: The experimental backend feature discovery now only considers default exports from packages. It no longer filters packages to include based on the package role, except that `'cli'` packages are ignored. However, the `"backstage"` field is still required in `package.json`. -- 3b30b179cb38: Add support for installing features as package imports, for example `backend.add(import('my-plugin'))`. -- Updated dependencies - - @backstage/config@1.1.0-next.2 - - @backstage/errors@1.2.2-next.0 - - @backstage/types@1.1.1-next.0 - - @backstage/plugin-permission-node@0.7.14-next.3 - - @backstage/backend-plugin-api@0.6.3-next.3 - - @backstage/backend-common@0.19.5-next.3 - - @backstage/backend-tasks@0.5.8-next.3 - - @backstage/cli-common@0.1.12 - - @backstage/cli-node@0.1.4-next.0 - - @backstage/config-loader@1.5.0-next.3 - - @backstage/plugin-auth-node@0.3.0-next.3 - -## 0.5.3-next.2 - -### Patch Changes - -- 37a20c7f14aa: Adds include and exclude configuration to feature discovery of backend packages - Adds alpha modules to feature discovery -- 3fc64b9e2f8f: Extension points are now tracked via their ID rather than reference, in order to support package duplication. -- b219d097b3f4: Backend startup will now fail if any circular service dependencies are detected. -- Updated dependencies - - @backstage/config-loader@1.5.0-next.2 - - @backstage/config@1.1.0-next.1 - - @backstage/backend-tasks@0.5.8-next.2 - - @backstage/backend-common@0.19.5-next.2 - - @backstage/plugin-auth-node@0.3.0-next.2 - - @backstage/plugin-permission-node@0.7.14-next.2 - - @backstage/backend-plugin-api@0.6.3-next.2 - - @backstage/cli-common@0.1.12 - - @backstage/cli-node@0.1.3 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.3-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/config@1.1.0-next.0 - - @backstage/backend-tasks@0.5.8-next.1 - - @backstage/backend-common@0.19.5-next.1 - - @backstage/backend-plugin-api@0.6.3-next.1 - - @backstage/config-loader@1.5.0-next.1 - - @backstage/plugin-auth-node@0.3.0-next.1 - - @backstage/plugin-permission-node@0.7.14-next.1 - - @backstage/cli-common@0.1.12 - - @backstage/cli-node@0.1.3 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.2-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-node@0.3.0-next.0 - - @backstage/backend-common@0.19.4-next.0 - - @backstage/config-loader@1.5.0-next.0 - - @backstage/backend-tasks@0.5.7-next.0 - - @backstage/backend-plugin-api@0.6.2-next.0 - - @backstage/cli-common@0.1.12 - - @backstage/cli-node@0.1.3 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - - @backstage/plugin-permission-node@0.7.13-next.0 - -## 0.5.0 - -### Minor Changes - -- b9c57a4f857e: **BREAKING**: Renamed `configServiceFactory` to `rootConfigServiceFactory`. -- a6d7983f349c: **BREAKING**: Removed the `services` option from `createBackend`. Service factories are now `BackendFeature`s and should be installed with `backend.add(...)` instead. The following should be migrated: - - ```ts - const backend = createBackend({ services: [myCustomServiceFactory] }); - ``` - - To instead pass the service factory via `backend.add(...)`: - - ```ts - const backend = createBackend(); - backend.add(customRootLoggerServiceFactory); - ``` - -### Patch Changes - -- e65c4896f755: Do not throw in backend.stop, if start failed -- c7aa4ff1793c: Allow modules to register extension points. -- 57a10c6c69cc: Add validation to make sure that extension points do not cross plugin boundaries. -- cc9256a33bcc: Added new experimental `featureDiscoveryServiceFactory`, available as an `/alpha` export. -- Updated dependencies - - @backstage/backend-common@0.19.2 - - @backstage/config-loader@1.4.0 - - @backstage/backend-plugin-api@0.6.0 - - @backstage/cli-node@0.1.3 - - @backstage/plugin-auth-node@0.2.17 - - @backstage/backend-tasks@0.5.5 - - @backstage/plugin-permission-node@0.7.11 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.5.0-next.2 - -### Patch Changes - -- e65c4896f755: Do not throw in backend.stop, if start failed -- cc9256a33bcc: Added new experimental `featureDiscoveryServiceFactory`, available as an `/alpha` export. -- Updated dependencies - - @backstage/backend-plugin-api@0.6.0-next.2 - - @backstage/backend-tasks@0.5.5-next.2 - - @backstage/backend-common@0.19.2-next.2 - - @backstage/plugin-permission-node@0.7.11-next.2 - - @backstage/plugin-auth-node@0.2.17-next.2 - - @backstage/config-loader@1.4.0-next.1 - -## 0.5.0-next.1 - -### Minor Changes - -- b9c57a4f857e: **BREAKING**: Renamed `configServiceFactory` to `rootConfigServiceFactory`. - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.2-next.1 - - @backstage/config-loader@1.4.0-next.1 - - @backstage/plugin-auth-node@0.2.17-next.1 - - @backstage/backend-plugin-api@0.6.0-next.1 - - @backstage/backend-tasks@0.5.5-next.1 - - @backstage/plugin-permission-node@0.7.11-next.1 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - -## 0.4.6-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/config-loader@1.4.0-next.0 - - @backstage/backend-common@0.19.2-next.0 - - @backstage/backend-plugin-api@0.5.5-next.0 - - @backstage/backend-tasks@0.5.5-next.0 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.8 - - @backstage/errors@1.2.1 - - @backstage/types@1.1.0 - - @backstage/plugin-auth-node@0.2.17-next.0 - - @backstage/plugin-permission-node@0.7.11-next.0 - -## 0.4.5 - -### Patch Changes - -- Updated dependencies - - @backstage/errors@1.2.1 - - @backstage/backend-common@0.19.1 - - @backstage/backend-plugin-api@0.5.4 - - @backstage/backend-tasks@0.5.4 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.8 - - @backstage/config-loader@1.3.2 - - @backstage/types@1.1.0 - - @backstage/plugin-auth-node@0.2.16 - - @backstage/plugin-permission-node@0.7.10 - -## 0.4.5-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/errors@1.2.1-next.0 - - @backstage/backend-common@0.19.1-next.0 - - @backstage/backend-plugin-api@0.5.4-next.0 - - @backstage/backend-tasks@0.5.4-next.0 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.8 - - @backstage/config-loader@1.3.2-next.0 - - @backstage/types@1.1.0 - - @backstage/plugin-auth-node@0.2.16-next.0 - - @backstage/plugin-permission-node@0.7.10-next.0 - -## 0.4.4 - -### Patch Changes - -- 3bb4158a8aa4: Switched startup strategy to initialize all plugins in parallel, as well as hook into the new startup lifecycle hooks. -- 68a21956ef52: Remove reference to deprecated import -- a5c5491ff50c: Use `durationToMilliseconds` from `@backstage/types` instead of our own -- 2c9f67e6f166: Introduced built-in middleware into the default `HttpService` implementation that throws a `ServiceNotAvailable` error when plugins aren't able to serve request. Also introduced a request stalling mechanism that pauses incoming request until plugins have been fully initialized. -- c4e8fefd9f13: Added handling of `ServiceUnavailableError` to error handling middleware. -- Updated dependencies - - @backstage/backend-common@0.19.0 - - @backstage/types@1.1.0 - - @backstage/config-loader@1.3.1 - - @backstage/errors@1.2.0 - - @backstage/backend-plugin-api@0.5.3 - - @backstage/backend-tasks@0.5.3 - - @backstage/plugin-auth-node@0.2.15 - - @backstage/plugin-permission-node@0.7.9 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.8 - -## 0.4.4-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.19.0-next.2 - - @backstage/backend-plugin-api@0.5.3-next.2 - - @backstage/backend-tasks@0.5.3-next.2 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/config-loader@1.3.1-next.1 - - @backstage/errors@1.2.0-next.0 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.15-next.2 - - @backstage/plugin-permission-node@0.7.9-next.2 - -## 0.4.4-next.1 - -### Patch Changes - -- 3bb4158a8aa4: Switched startup strategy to initialize all plugins in parallel, as well as hook into the new startup lifecycle hooks. -- 2c9f67e6f166: Introduced built-in middleware into the default `HttpService` implementation that throws a `ServiceNotAvailable` error when plugins aren't able to serve request. Also introduced a request stalling mechanism that pauses incoming request until plugins have been fully initialized. -- c4e8fefd9f13: Added handling of `ServiceUnavailableError` to error handling middleware. -- Updated dependencies - - @backstage/backend-common@0.19.0-next.1 - - @backstage/errors@1.2.0-next.0 - - @backstage/backend-plugin-api@0.5.3-next.1 - - @backstage/backend-tasks@0.5.3-next.1 - - @backstage/plugin-auth-node@0.2.15-next.1 - - @backstage/plugin-permission-node@0.7.9-next.1 - - @backstage/config-loader@1.3.1-next.1 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/types@1.0.2 - -## 0.4.4-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/config-loader@1.3.1-next.0 - - @backstage/backend-common@0.18.6-next.0 - - @backstage/config@1.0.7 - - @backstage/backend-plugin-api@0.5.3-next.0 - - @backstage/backend-tasks@0.5.3-next.0 - - @backstage/cli-common@0.1.12 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.15-next.0 - - @backstage/plugin-permission-node@0.7.9-next.0 - -## 0.4.3 - -### Patch Changes - -- cf13b482f9e: Switch `configServiceFactory` to use `ConfigSources` from `@backstage/config-loader` to load config. -- Updated dependencies - - @backstage/backend-common@0.18.5 - - @backstage/config-loader@1.3.0 - - @backstage/plugin-permission-node@0.7.8 - - @backstage/backend-tasks@0.5.2 - - @backstage/plugin-auth-node@0.2.14 - - @backstage/backend-plugin-api@0.5.2 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.4.3-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.5-next.1 - - @backstage/backend-tasks@0.5.2-next.1 - - @backstage/plugin-auth-node@0.2.14-next.1 - - @backstage/plugin-permission-node@0.7.8-next.1 - - @backstage/backend-plugin-api@0.5.2-next.1 - - @backstage/config-loader@1.3.0-next.0 - - @backstage/config@1.0.7 - -## 0.4.3-next.0 - -### Patch Changes - -- cf13b482f9e: Switch `configServiceFactory` to use `ConfigSources` from `@backstage/config-loader` to load config. -- Updated dependencies - - @backstage/backend-common@0.18.5-next.0 - - @backstage/config-loader@1.3.0-next.0 - - @backstage/plugin-permission-node@0.7.8-next.0 - - @backstage/backend-tasks@0.5.2-next.0 - - @backstage/plugin-auth-node@0.2.14-next.0 - - @backstage/backend-plugin-api@0.5.2-next.0 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.4.2 - -### Patch Changes - -- 5c7ce585824: Allow an additionalConfig to be provided to loadBackendConfig that fetches config values during runtime. -- 8cce2205a39: Register unhandled rejection and uncaught exception handlers to avoid backend crashes. -- Updated dependencies - - @backstage/backend-common@0.18.4 - - @backstage/config-loader@1.2.0 - - @backstage/plugin-permission-node@0.7.7 - - @backstage/backend-tasks@0.5.1 - - @backstage/plugin-auth-node@0.2.13 - - @backstage/backend-plugin-api@0.5.1 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - -## 0.4.2-next.2 - -### Patch Changes - -- 5c7ce585824: Allow an additionalConfig to be provided to loadBackendConfig that fetches config values during runtime. -- Updated dependencies - - @backstage/backend-common@0.18.4-next.2 - - @backstage/plugin-permission-node@0.7.7-next.2 - - @backstage/backend-plugin-api@0.5.1-next.2 - - @backstage/backend-tasks@0.5.1-next.2 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/config-loader@1.1.9 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.13-next.2 - -## 0.4.2-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-permission-node@0.7.7-next.1 - - @backstage/backend-tasks@0.5.1-next.1 - - @backstage/backend-common@0.18.4-next.1 - - @backstage/backend-plugin-api@0.5.1-next.1 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/config-loader@1.1.9 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.13-next.1 - -## 0.4.2-next.0 - -### Patch Changes - -- 8cce2205a39: Register unhandled rejection and uncaught exception handlers to avoid backend crashes. -- Updated dependencies - - @backstage/backend-common@0.18.4-next.0 - - @backstage/config@1.0.7 - - @backstage/backend-plugin-api@0.5.1-next.0 - - @backstage/backend-tasks@0.5.1-next.0 - - @backstage/cli-common@0.1.12 - - @backstage/config-loader@1.1.9 - - @backstage/errors@1.1.5 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.13-next.0 - - @backstage/plugin-permission-node@0.7.7-next.0 - -## 0.4.1 - -### Patch Changes - -- 928a12a9b3e: Internal refactor of `/alpha` exports. -- 482dae5de1c: Updated link to docs. -- 915e46622cf: Add support for `NotImplementedError`, properly returning 501 as status code. -- Updated dependencies - - @backstage/plugin-permission-node@0.7.6 - - @backstage/plugin-auth-node@0.2.12 - - @backstage/backend-tasks@0.5.0 - - @backstage/backend-common@0.18.3 - - @backstage/errors@1.1.5 - - @backstage/backend-plugin-api@0.5.0 - - @backstage/config-loader@1.1.9 - - @backstage/cli-common@0.1.12 - - @backstage/config@1.0.7 - - @backstage/types@1.0.2 - -## 0.4.1-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/plugin-auth-node@0.2.12-next.2 - - @backstage/backend-tasks@0.5.0-next.2 - - @backstage/backend-common@0.18.3-next.2 - - @backstage/backend-plugin-api@0.4.1-next.2 - - @backstage/plugin-permission-node@0.7.6-next.2 - - @backstage/config@1.0.7-next.0 - -## 0.4.1-next.1 - -### Patch Changes - -- 482dae5de1c: Updated link to docs. -- 915e46622cf: Add support for `NotImplementedError`, properly returning 501 as status code. -- Updated dependencies - - @backstage/plugin-permission-node@0.7.6-next.1 - - @backstage/errors@1.1.5-next.0 - - @backstage/backend-common@0.18.3-next.1 - - @backstage/config-loader@1.1.9-next.0 - - @backstage/plugin-auth-node@0.2.12-next.1 - - @backstage/backend-plugin-api@0.4.1-next.1 - - @backstage/backend-tasks@0.4.4-next.1 - - @backstage/cli-common@0.1.12-next.0 - - @backstage/config@1.0.7-next.0 - - @backstage/types@1.0.2 - -## 0.4.1-next.0 - -### Patch Changes - -- 928a12a9b3: Internal refactor of `/alpha` exports. -- Updated dependencies - - @backstage/backend-tasks@0.4.4-next.0 - - @backstage/backend-plugin-api@0.4.1-next.0 - - @backstage/backend-common@0.18.3-next.0 - - @backstage/cli-common@0.1.11 - - @backstage/config@1.0.6 - - @backstage/config-loader@1.1.8 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.12-next.0 - - @backstage/plugin-permission-node@0.7.6-next.0 - -## 0.4.0 - -### Minor Changes - -- 01a075ec1d: **BREAKING**: Renamed `RootHttpRouterConfigureOptions` to `RootHttpRouterConfigureContext`, and removed the unused type `ServiceOrExtensionPoint`. -- 4ae71b7f2e: **BREAKING** Renaming `*Factory` exports to `*ServiceFactory` instead. For example `configFactory` now is exported as `configServiceFactory`. -- d31d8e00b3: **BREAKING** `HttpServerCertificateOptions` when specified with a `key` and `cert` should also have the `type: 'pem'` instead of `type: 'plain'` - -### Patch Changes - -- a18da2f8b5: Fixed an issue were the log redaction didn't properly escape RegExp characters. -- 5febb216fe: Updated to match the new `CacheService` interface. -- e716946103: Updated usage of the lifecycle service. -- f60cca9da1: Updated database factory to pass service deps required for restoring database state during development. -- 610d65e143: Updates to match new `BackendFeature` type. -- 725383f69d: Tweaked messaging in the README. -- b86efa2d04: Updated usage of `ServiceFactory`. -- ab22515647: The shutdown signal handlers are now installed as part of the backend instance rather than the lifecycle service, and explicitly cause the process to exit. -- b729f9f31f: Moved the options of the `config` and `rootHttpRouter` services out to the factories themselves, where they belong -- ed8b5967d7: `HttpRouterFactoryOptions.getPath` is now optional as a default value is always provided in the factory. -- 71a5ec0f06: Updated usages of `LogMeta`. -- Updated dependencies - - @backstage/backend-plugin-api@0.4.0 - - @backstage/backend-common@0.18.2 - - @backstage/backend-tasks@0.4.3 - - @backstage/cli-common@0.1.11 - - @backstage/config@1.0.6 - - @backstage/config-loader@1.1.8 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.11 - - @backstage/plugin-permission-node@0.7.5 - -## 0.4.0-next.2 - -### Minor Changes - -- 01a075ec1d: **BREAKING**: Renamed `RootHttpRouterConfigureOptions` to `RootHttpRouterConfigureContext`, and removed the unused type `ServiceOrExtensionPoint`. -- 4ae71b7f2e: **BREAKING** Renaming `*Factory` exports to `*ServiceFactory` instead. For example `configFactory` now is exported as `configServiceFactory`. -- d31d8e00b3: **BREAKING** `HttpServerCertificateOptions` when specified with a `key` and `cert` should also have the `type: 'pem'` instead of `type: 'plain'` - -### Patch Changes - -- e716946103: Updated usage of the lifecycle service. -- f60cca9da1: Updated database factory to pass service deps required for restoring database state during development. -- 610d65e143: Updates to match new `BackendFeature` type. -- ab22515647: The shutdown signal handlers are now installed as part of the backend instance rather than the lifecycle service, and explicitly cause the process to exit. -- b729f9f31f: Moved the options of the `config` and `rootHttpRouter` services out to the factories themselves, where they belong -- 71a5ec0f06: Updated usages of `LogMeta`. -- Updated dependencies - - @backstage/backend-plugin-api@0.4.0-next.2 - - @backstage/backend-common@0.18.2-next.2 - - @backstage/backend-tasks@0.4.3-next.2 - - @backstage/plugin-auth-node@0.2.11-next.2 - - @backstage/plugin-permission-node@0.7.5-next.2 - - @backstage/cli-common@0.1.11 - - @backstage/config@1.0.6 - - @backstage/config-loader@1.1.8 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - -## 0.3.2-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.18.2-next.1 - - @backstage/backend-plugin-api@0.3.2-next.1 - - @backstage/backend-tasks@0.4.3-next.1 - - @backstage/cli-common@0.1.11 - - @backstage/config@1.0.6 - - @backstage/config-loader@1.1.8 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.11-next.1 - - @backstage/plugin-permission-node@0.7.5-next.1 - -## 0.3.2-next.0 - -### Patch Changes - -- a18da2f8b5: Fixed an issue were the log redaction didn't properly escape RegExp characters. -- ed8b5967d7: `HttpRouterFactoryOptions.getPath` is now optional as a default value is always provided in the factory. -- Updated dependencies - - @backstage/backend-common@0.18.2-next.0 - - @backstage/backend-tasks@0.4.3-next.0 - - @backstage/plugin-auth-node@0.2.11-next.0 - - @backstage/plugin-permission-node@0.7.5-next.0 - - @backstage/backend-plugin-api@0.3.2-next.0 - -## 0.3.0 - -### Minor Changes - -- 02b119ff93: **BREAKING**: The `httpRouterFactory` now accepts a `getPath` option rather than `indexPlugin`. To set up custom index path, configure the new `rootHttpRouterFactory` with a custom `indexPath` instead. - - Added an implementation for the new `rootHttpRouterServiceRef`. - -### Patch Changes - -- ecc6bfe4c9: Use new `ServiceFactoryOrFunction` type. -- b99c030f1b: Moved over implementation of the root HTTP service from `@backstage/backend-common`, and replaced the `middleware` option with a `configure` callback option. -- 170282ece6: Fixed a bug in the default token manager factory where it created multiple incompatible instances. -- 843a0a158c: Added service factory for the new core identity service. -- 150a7dd790: An error will now be thrown if attempting to override the plugin metadata service. -- 483e907eaf: Internal updates of `createServiceFactory` from `@backstage/backend-plugin-api`. -- 015a6dced6: The `createSpecializedBackend` function will now throw an error if duplicate service implementations are provided. -- e3fca10038: Tweaked the plugin logger to use `plugin` as the label for the plugin ID, rather than `pluginId`. -- ecbec4ec4c: Internal refactor to match new options pattern in the experimental backend system. -- 51b7a7ed07: Exported the default root HTTP router implementation as `DefaultRootHttpRouter`. It only implements the routing layer and needs to be exposed via an HTTP server similar to the built-in setup in the `rootHttpRouterFactory`. -- 0e63aab311: Moved over logging and configuration loading implementations from `@backstage/backend-common`. There is a now `WinstonLogger` which implements the `RootLoggerService` through Winston with accompanying utilities. For configuration the `loadBackendConfig` function has been moved over, but it now instead returns an object with a `config` property. -- 8e06f3cf00: Switched imports of `loggerToWinstonLogger` to `@backstage/backend-common`. -- 3b8fd4169b: Internal folder structure refactor. -- 6cfd4d7073: Updated implementations for the new `RootLifecycleService`. -- Updated dependencies - - @backstage/backend-plugin-api@0.3.0 - - @backstage/backend-common@0.18.0 - - @backstage/backend-tasks@0.4.1 - - @backstage/config@1.0.6 - - @backstage/cli-common@0.1.11 - - @backstage/config-loader@1.1.8 - - @backstage/errors@1.1.4 - - @backstage/types@1.0.2 - - @backstage/plugin-auth-node@0.2.9 - - @backstage/plugin-permission-node@0.7.3 - -## 0.3.0-next.1 - -### Minor Changes - -- 02b119ff93: **BREAKING**: The `httpRouterFactory` now accepts a `getPath` option rather than `indexPlugin`. To set up custom index path, configure the new `rootHttpRouterFactory` with a custom `indexPath` instead. - - Added an implementation for the new `rootHttpRouterServiceRef`. - -### Patch Changes - -- ecc6bfe4c9: Use new `ServiceFactoryOrFunction` type. -- b99c030f1b: Moved over implementation of the root HTTP service from `@backstage/backend-common`, and replaced the `middleware` option with a `configure` callback option. -- 150a7dd790: An error will now be thrown if attempting to override the plugin metadata service. -- 015a6dced6: The `createSpecializedBackend` function will now throw an error if duplicate service implementations are provided. -- e3fca10038: Tweaked the plugin logger to use `plugin` as the label for the plugin ID, rather than `pluginId`. -- 8e06f3cf00: Switched imports of `loggerToWinstonLogger` to `@backstage/backend-common`. -- Updated dependencies - - @backstage/backend-plugin-api@0.3.0-next.1 - - @backstage/backend-common@0.18.0-next.1 - - @backstage/backend-tasks@0.4.1-next.1 - - @backstage/plugin-permission-node@0.7.3-next.1 - - @backstage/config@1.0.6-next.0 - - @backstage/errors@1.1.4 - -## 0.2.5-next.0 - -### Patch Changes - -- 6cfd4d7073: Updated implementations for the new `RootLifecycleService`. -- Updated dependencies - - @backstage/backend-plugin-api@0.2.1-next.0 - - @backstage/backend-common@0.18.0-next.0 - - @backstage/backend-tasks@0.4.1-next.0 - - @backstage/errors@1.1.4 - - @backstage/plugin-permission-node@0.7.3-next.0 - -## 0.2.4 - -### Patch Changes - -- cb1c2781c0: Updated logger implementations to match interface changes. -- 884d749b14: Refactored to use `coreServices` from `@backstage/backend-plugin-api`. -- afa3bf5657: Added `.stop()` method to `Backend`. -- d6dbf1792b: Added `lifecycleFactory` implementation. -- 05a928e296: Updated usages of types from `@backstage/backend-plugin-api`. -- 5260d8fc7d: Root scoped services are now always initialized, regardless of whether they're used by any features. -- Updated dependencies - - @backstage/backend-common@0.17.0 - - @backstage/backend-tasks@0.4.0 - - @backstage/plugin-permission-node@0.7.2 - - @backstage/errors@1.1.4 - - @backstage/backend-plugin-api@0.2.0 - -## 0.2.4-next.3 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-tasks@0.4.0-next.3 - - @backstage/plugin-permission-node@0.7.2-next.3 - - @backstage/backend-common@0.17.0-next.3 - - @backstage/backend-plugin-api@0.2.0-next.3 - - @backstage/errors@1.1.4-next.1 - -## 0.2.4-next.2 - -### Patch Changes - -- 884d749b14: Refactored to use `coreServices` from `@backstage/backend-plugin-api`. -- Updated dependencies - - @backstage/backend-common@0.17.0-next.2 - - @backstage/backend-plugin-api@0.2.0-next.2 - - @backstage/backend-tasks@0.4.0-next.2 - - @backstage/plugin-permission-node@0.7.2-next.2 - - @backstage/errors@1.1.4-next.1 - -## 0.2.4-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.17.0-next.1 - - @backstage/backend-tasks@0.4.0-next.1 - - @backstage/backend-plugin-api@0.1.5-next.1 - - @backstage/plugin-permission-node@0.7.2-next.1 - - @backstage/errors@1.1.4-next.1 - -## 0.2.4-next.0 - -### Patch Changes - -- d6dbf1792b: Added `lifecycleFactory` implementation. -- Updated dependencies - - @backstage/backend-common@0.16.1-next.0 - - @backstage/plugin-permission-node@0.7.2-next.0 - - @backstage/backend-plugin-api@0.1.5-next.0 - - @backstage/backend-tasks@0.3.8-next.0 - - @backstage/errors@1.1.4-next.0 - -## 0.2.3 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.16.0 - - @backstage/backend-tasks@0.3.7 - - @backstage/backend-plugin-api@0.1.4 - - @backstage/plugin-permission-node@0.7.1 - - @backstage/errors@1.1.3 - -## 0.2.3-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.16.0-next.1 - - @backstage/backend-plugin-api@0.1.4-next.1 - - @backstage/backend-tasks@0.3.7-next.1 - - @backstage/plugin-permission-node@0.7.1-next.1 - - @backstage/errors@1.1.3-next.0 - -## 0.2.3-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.16.0-next.0 - - @backstage/backend-tasks@0.3.7-next.0 - - @backstage/backend-plugin-api@0.1.4-next.0 - - @backstage/plugin-permission-node@0.7.1-next.0 - - @backstage/errors@1.1.3-next.0 - -## 0.2.2 - -### Patch Changes - -- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. -- 45857bffae: Properly export `rootLoggerFactory`. -- Updated dependencies - - @backstage/backend-common@0.15.2 - - @backstage/backend-tasks@0.3.6 - - @backstage/plugin-permission-node@0.7.0 - - @backstage/backend-plugin-api@0.1.3 - - @backstage/errors@1.1.2 - -## 0.2.2-next.2 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-tasks@0.3.6-next.2 - - @backstage/backend-common@0.15.2-next.2 - - @backstage/plugin-permission-node@0.7.0-next.2 - - @backstage/backend-plugin-api@0.1.3-next.2 - - @backstage/errors@1.1.2-next.2 - -## 0.2.2-next.1 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.15.2-next.1 - - @backstage/backend-plugin-api@0.1.3-next.1 - - @backstage/backend-tasks@0.3.6-next.1 - - @backstage/errors@1.1.2-next.1 - - @backstage/plugin-permission-node@0.6.6-next.1 - -## 0.2.2-next.0 - -### Patch Changes - -- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. -- 45857bffae: Properly export `rootLoggerFactory`. -- Updated dependencies - - @backstage/backend-plugin-api@0.1.3-next.0 - - @backstage/backend-common@0.15.2-next.0 - - @backstage/backend-tasks@0.3.6-next.0 - - @backstage/plugin-permission-node@0.6.6-next.0 - - @backstage/errors@1.1.2-next.0 - -## 0.2.1 - -### Patch Changes - -- 2c57c0c499: Made `ApiRef.defaultFactory` internal. -- 854ba37357: Updated to support new `ServiceFactory` formats. -- af6bb42c68: Updated `ServiceRegistry` to not initialize factories more than once. -- 409ed984e8: Updated service implementations and backend wiring to support scoped service. -- de3347ca74: Updated usages of `ServiceFactory`. -- 1f384c5644: Improved error messaging when failing to instantiate services. -- Updated dependencies - - @backstage/backend-plugin-api@0.1.2 - - @backstage/backend-common@0.15.1 - - @backstage/plugin-permission-node@0.6.5 - - @backstage/backend-tasks@0.3.5 - - @backstage/errors@1.1.1 - -## 0.2.1-next.2 - -### Patch Changes - -- 854ba37357: Updated to support new `ServiceFactory` formats. -- 409ed984e8: Updated service implementations and backend wiring to support scoped service. -- Updated dependencies - - @backstage/backend-plugin-api@0.1.2-next.2 - - @backstage/errors@1.1.1-next.0 - - @backstage/backend-common@0.15.1-next.3 - - @backstage/backend-tasks@0.3.5-next.1 - - @backstage/plugin-permission-node@0.6.5-next.3 - -## 0.2.1-next.1 - -### Patch Changes - -- 2c57c0c499: Made `ApiRef.defaultFactory` internal. -- af6bb42c68: Updated `ServiceRegistry` to not initialize factories more than once. -- 1f384c5644: Improved error messaging when failing to instantiate services. -- Updated dependencies - - @backstage/backend-plugin-api@0.1.2-next.1 - - @backstage/backend-common@0.15.1-next.2 - - @backstage/plugin-permission-node@0.6.5-next.2 - -## 0.2.1-next.0 - -### Patch Changes - -- de3347ca74: Updated usages of `ServiceFactory`. -- Updated dependencies - - @backstage/backend-common@0.15.1-next.0 - - @backstage/backend-tasks@0.3.5-next.0 - - @backstage/backend-plugin-api@0.1.2-next.0 - - @backstage/plugin-permission-node@0.6.5-next.0 - -## 0.2.0 - -### Minor Changes - -- 5df230d48c: Introduced a new `backend-defaults` package carrying `createBackend` which was previously exported from `backend-app-api`. - The `backend-app-api` package now exports the `createSpecializedBacked` that does not add any service factories by default. - -### Patch Changes - -- 0599732ec0: Refactored experimental backend system with new type names. -- Updated dependencies - - @backstage/backend-common@0.15.0 - - @backstage/backend-plugin-api@0.1.1 - - @backstage/backend-tasks@0.3.4 - - @backstage/plugin-permission-node@0.6.4 - -## 0.1.1-next.0 - -### Patch Changes - -- Updated dependencies - - @backstage/backend-common@0.15.0-next.0 - - @backstage/backend-tasks@0.3.4-next.0 - - @backstage/backend-plugin-api@0.1.1-next.0 - - @backstage/plugin-permission-node@0.6.4-next.0 - -## 0.1.0 - -### Minor Changes - -- 91c1d12123: Add initial plumbing for creating backends using the experimental backend framework. - - This package is highly **EXPERIMENTAL** and should not be used in production. - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.1.0 - - @backstage/backend-common@0.14.1 - - @backstage/plugin-permission-node@0.6.3 - - @backstage/backend-tasks@0.3.3 - -## 0.1.0-next.0 - -### Minor Changes - -- 91c1d12123: Add initial plumbing for creating backends using the experimental backend framework. - - This package is highly **EXPERIMENTAL** and should not be used in production. - -### Patch Changes - -- Updated dependencies - - @backstage/backend-plugin-api@0.1.0-next.0 - - @backstage/backend-common@0.14.1-next.3 - - @backstage/plugin-permission-node@0.6.3-next.2 - - @backstage/backend-tasks@0.3.3-next.3 - ## @backstage/frontend-app-api@0.7.0-next.2 ### Minor Changes From a9101c283d7182127d8ef91badb2b55756517b08 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 8 May 2024 15:23:53 +0200 Subject: [PATCH 310/567] app-next: fix empty homepage Signed-off-by: Patrik Oldsberg --- packages/app-next/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 83b921aea1..c911a01258 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -80,7 +80,7 @@ TODO: const homePageExtension = createExtension({ name: 'myhomepage', - attachTo: { id: 'home', input: 'props' }, + attachTo: { id: 'page:home', input: 'props' }, output: { children: coreExtensionData.reactElement, title: titleExtensionDataRef, From 2adb57ffdcf51b724407c29f7c58f766dd0175b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 13:30:16 +0000 Subject: [PATCH 311/567] chore(deps): update dependency @types/diff to v5.2.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6cee25b42b..6a60bab02e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17069,9 +17069,9 @@ __metadata: linkType: hard "@types/diff@npm:^5.0.0": - version: 5.2.0 - resolution: "@types/diff@npm:5.2.0" - checksum: 07e20ba25d15b997758cc248628bb1e6459e7b396c868f176dee1e6a5d1dfcdf1e186fb5bf5e67128d7f55a11e989cff2ec656de089e15ed4a44e654c5c1fda3 + version: 5.2.1 + resolution: "@types/diff@npm:5.2.1" + checksum: 5983a323177bd691cb2194f5d55b960cd20a9c8fec653b4b038760c5809627cc9ea3578fdf10119ccbefefef193ea925f2817136eb97b17388f66b16c8480a8a languageName: node linkType: hard From 07a99e5a8b931b3a5e1b4235568a99c88f4f853b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 14:21:54 +0000 Subject: [PATCH 312/567] chore(deps): update dependency @types/jquery to v3.5.30 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6a60bab02e..5c700c6fdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17438,11 +17438,11 @@ __metadata: linkType: hard "@types/jquery@npm:^3.3.34": - version: 3.5.29 - resolution: "@types/jquery@npm:3.5.29" + version: 3.5.30 + resolution: "@types/jquery@npm:3.5.30" dependencies: "@types/sizzle": "*" - checksum: 5e959762d6f7050b07b4387b6507a308113384566a77cfc4f8d0f54c2fb0a79f6bc8c057706c6aa4840cde56f32ad0e5814fb53c5f078c5db9e01670a1ecd535 + checksum: 4594d10fa9b347062883d254a23c9259ae814ef5989ce1985f093dcc7ad4475e324ac3343aef10599c478ea4951726f0e7f79d8ed471ab04de394b7e724d6d13 languageName: node linkType: hard From c88b3ee688617d6d993a3a76c438825dcbad1182 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 15:19:26 +0000 Subject: [PATCH 313/567] chore(deps): update dependency @types/webpack-env to v1.18.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- microsite/yarn.lock | 6 +++--- yarn.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 3ca3e2837a..f25dda1cbd 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -3237,9 +3237,9 @@ __metadata: linkType: hard "@types/webpack-env@npm:^1.18.0": - version: 1.18.4 - resolution: "@types/webpack-env@npm:1.18.4" - checksum: f195b3ae974ac3b631477b57737dad7b6c44ecca86770cf3c29f284e02961c9f2dfc619e3e253d8c23966864cb052b1e8437e9834ede32ac97972e6e2235bb51 + version: 1.18.5 + resolution: "@types/webpack-env@npm:1.18.5" + checksum: 4ca8eb4c44e1e1807c3e245442fce7aaf2816a163056de9436bbac44cc47c8bc5b1c9a330dc05748d6616431b1fb5bd5379733fb1da0b78d03c59f4ec824c184 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index 5c700c6fdc..10928fcdc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18542,9 +18542,9 @@ __metadata: linkType: hard "@types/webpack-env@npm:^1.15.2, @types/webpack-env@npm:^1.15.3": - version: 1.18.4 - resolution: "@types/webpack-env@npm:1.18.4" - checksum: f195b3ae974ac3b631477b57737dad7b6c44ecca86770cf3c29f284e02961c9f2dfc619e3e253d8c23966864cb052b1e8437e9834ede32ac97972e6e2235bb51 + version: 1.18.5 + resolution: "@types/webpack-env@npm:1.18.5" + checksum: 4ca8eb4c44e1e1807c3e245442fce7aaf2816a163056de9436bbac44cc47c8bc5b1c9a330dc05748d6616431b1fb5bd5379733fb1da0b78d03c59f4ec824c184 languageName: node linkType: hard From 96e30c54ab9dd8c966b9495fdcd8e13a7f6d7545 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:12:28 -0700 Subject: [PATCH 314/567] fix: rename uri to url Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 6 +++--- .../src/services/implementations/auth/external/jwks.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 9ecd39ba8f..e678c82477 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -96,7 +96,7 @@ backend: externalAccess: - type: jwks options: - uri: https://example.com/.well-known/jwks.json + url: https://example.com/.well-known/jwks.json issuers: - https://example.com algorithms: @@ -105,12 +105,12 @@ backend: - example - type: jwks options: - uri: https://another-example.com/.well-known/jwks.json + url: https://another-example.com/.well-known/jwks.json issuers: - https://example.com ``` -The URI should point at an unauthenticated endpoint that returns the JWKS. +The URL should point at an unauthenticated endpoint that returns the JWKS. Issuers specifies the issuer(s) of the JWT that the authenticating app will accept. Passed JWTs must have an `iss` claim which matches one of the specified issuers. diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 5c3738504d..070f33ed53 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -28,26 +28,26 @@ export class JWKSHandler implements TokenHandler { algorithms: string[]; audiences: string[] | string; issuers: string[]; - uri: string; + url: string; }> = []; add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms') ?? []; const issuers = options.getOptionalStringArray('issuers') ?? []; const audiences = options.getOptionalStringArray('audiences') ?? ''; - const uri = options.getString('uri'); + const url = options.getString('url'); - if (!uri.match(/^\S+$/)) { + if (!url.match(/^\S+$/)) { throw new Error('Illegal URI, must be a set of non-space characters'); } - this.#entries.push({ algorithms, audiences, issuers, uri }); + this.#entries.push({ algorithms, audiences, issuers, url }); } async verifyToken(token: string) { for (const entry of this.#entries) { try { - const jwks = createRemoteJWKSet(new URL(entry.uri)); + const jwks = createRemoteJWKSet(new URL(entry.url)); const { payload: { sub }, } = await jwtVerify(token, jwks, { From 8443332f72f5c90bf53724433bcef44ec755bba7 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:18:06 -0700 Subject: [PATCH 315/567] fix: default to undefined for algo, iss and aud fields if not set in config Signed-off-by: Ryan Hanchett --- .../services/implementations/auth/external/jwks.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 070f33ed53..dd3df07435 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -25,20 +25,20 @@ import { TokenHandler } from './types'; */ export class JWKSHandler implements TokenHandler { #entries: Array<{ - algorithms: string[]; - audiences: string[] | string; - issuers: string[]; + algorithms: string[] | undefined; + audiences: string[] | undefined; + issuers: string[] | undefined; url: string; }> = []; add(options: Config) { - const algorithms = options.getOptionalStringArray('algorithms') ?? []; - const issuers = options.getOptionalStringArray('issuers') ?? []; - const audiences = options.getOptionalStringArray('audiences') ?? ''; + const algorithms = options.getOptionalStringArray('algorithms'); + const issuers = options.getOptionalStringArray('issuers'); + const audiences = options.getOptionalStringArray('audiences'); const url = options.getString('url'); if (!url.match(/^\S+$/)) { - throw new Error('Illegal URI, must be a set of non-space characters'); + throw new Error('Illegal URL, must be a set of non-space characters'); } this.#entries.push({ algorithms, audiences, issuers, url }); From e5ade53cd97903b7f26e8247ba1fdc52ebdb5656 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:41:33 -0700 Subject: [PATCH 316/567] feat: add subjectPrefix config Signed-off-by: Ryan Hanchett --- docs/auth/service-to-service-auth.md | 5 +++- .../implementations/auth/external/jwks.ts | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index e678c82477..0b2aa369ec 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -103,6 +103,7 @@ backend: - RS256 audiences: - example + subjectPrefix: custom-prefix - type: jwks options: url: https://another-example.com/.well-known/jwks.json @@ -125,7 +126,9 @@ For additional details regarding the JWKS configuration, please consult your aut provider's documentation. The subject returned from the token verification will become part of the -credentials object that the request recipient plugins get. +credentials object that the request recipient plugins get. All subjects will have the prefix +`external:`, but you can also provide a custom subjectPrefix which will get appended before the +subject returned from your JWKS service (ex. `external:custom-prefix:sub`). ## Legacy Tokens diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index dd3df07435..d734cbf984 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -25,29 +25,31 @@ import { TokenHandler } from './types'; */ export class JWKSHandler implements TokenHandler { #entries: Array<{ - algorithms: string[] | undefined; - audiences: string[] | undefined; - issuers: string[] | undefined; - url: string; + algorithms?: string[]; + audiences?: string[]; + issuers?: string[]; + subjectPrefix?: string; + url: URL; }> = []; add(options: Config) { const algorithms = options.getOptionalStringArray('algorithms'); const issuers = options.getOptionalStringArray('issuers'); const audiences = options.getOptionalStringArray('audiences'); - const url = options.getString('url'); + const subjectPrefix = options.getOptionalString('subjectPrefix'); + const url = new URL(options.getString('url')); - if (!url.match(/^\S+$/)) { + if (!options.getString('url').match(/^\S+$/)) { throw new Error('Illegal URL, must be a set of non-space characters'); } - this.#entries.push({ algorithms, audiences, issuers, url }); + this.#entries.push({ algorithms, audiences, issuers, subjectPrefix, url }); } async verifyToken(token: string) { for (const entry of this.#entries) { try { - const jwks = createRemoteJWKSet(new URL(entry.url)); + const jwks = createRemoteJWKSet(entry.url); const { payload: { sub }, } = await jwtVerify(token, jwks, { @@ -57,7 +59,11 @@ export class JWKSHandler implements TokenHandler { }); if (sub) { - return { subject: sub }; + if (entry.subjectPrefix) { + return { subject: `external:${entry.subjectPrefix}:${sub}` }; + } + + return { subject: `external:${sub}` }; } } catch { continue; From e54e0c47c551c1bec224591cb16a90333cd2747b Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Wed, 8 May 2024 09:46:56 -0700 Subject: [PATCH 317/567] test: fix tests, add new test for custom subject prefix Signed-off-by: Ryan Hanchett --- .../auth/external/jwks.test.ts | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index 95df6f56ff..4cbdcb1cb0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -100,7 +100,7 @@ describe('JWKSHandler', () => { it('verifies token with valid entry', async () => { const validEntry = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: [mockBaseUrl], audiences: ['backstage'], @@ -115,19 +115,19 @@ describe('JWKSHandler', () => { const result = await jwksHandler.verifyToken(token); - expect(result).toEqual({ subject: mockSubject }); + expect(result).toEqual({ subject: `external:${mockSubject}` }); }); it('skips invalid entry and continues verification', async () => { const invalidEntry = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: ['fakeIssuer'], audiences: ['fakeAud'], }; const validEntry = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: ['multiple-issuers', mockBaseUrl], audiences: ['multiple-audiences', 'backstage'], @@ -143,19 +143,19 @@ describe('JWKSHandler', () => { const result = await jwksHandler.verifyToken(token); - expect(result).toEqual({ subject: mockSubject }); + expect(result).toEqual({ subject: `external:${mockSubject}` }); }); it('returns undefined if no valid entry found', async () => { const invalidEntry1 = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['RS256'], issuers: [mockBaseUrl], audiences: [], }; const invalidEntry2 = { - uri: `${mockBaseUrl}/.well-known/jwks.json`, + url: `${mockBaseUrl}/.well-known/jwks.json`, algorithms: ['HS256'], issuers: [], audiences: ['backstage'], @@ -180,21 +180,44 @@ describe('JWKSHandler', () => { expect(() => { jwksHandler.add( new ConfigReader({ - uri: 'https://exampl e.com/jwks', + url: 'https://exampl e.com/jwks', }), ); - }).toThrow('Illegal URI, must be a set of non-space characters'); + }).toThrow('Invalid URL'); expect(() => { jwksHandler.add( new ConfigReader({ - uri: 'https://example.com/jwks\n', + url: 'https://example.com/jwks\n', }), ); - }).toThrow('Illegal URI, must be a set of non-space characters'); + }).toThrow('Illegal URL, must be a set of non-space characters'); }); it('gracefully handles no added tokens', async () => { const handler = new JWKSHandler(); await expect(handler.verifyToken('ghi')).resolves.toBeUndefined(); }); + + it('uses custom subject prefix if provided', async () => { + const validEntry = { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithms: ['RS256'], + issuers: [mockBaseUrl], + audiences: ['backstage'], + subjectPrefix: 'custom-prefix', + }; + const jwksHandler = new JWKSHandler(); + + jwksHandler.add(new ConfigReader(validEntry)); + + const token = await factory.issueToken({ + claims: { sub: mockSubject }, + }); + + const result = await jwksHandler.verifyToken(token); + + expect(result).toEqual({ + subject: `external:${validEntry.subjectPrefix}:${mockSubject}`, + }); + }); }); From 9a0c4795b7a9dc5b31e088bec4e3e8b127d6ccb7 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Wed, 8 May 2024 13:39:52 -0400 Subject: [PATCH 318/567] docs(scaffolder-backend): update documentation with new scaffolder permissions Signed-off-by: Frank Kong --- ...der-tasks-parameters-steps-and-actions.md} | 67 +++++++++++++++++-- microsite/sidebars.json | 2 +- 2 files changed, 63 insertions(+), 6 deletions(-) rename docs/features/software-templates/{authorizing-parameters-steps-and-actions.md => authorizing-scaffolder-tasks-parameters-steps-and-actions.md} (77%) diff --git a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md similarity index 77% rename from docs/features/software-templates/authorizing-parameters-steps-and-actions.md rename to docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md index 072f750a14..76bbf39d30 100644 --- a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md @@ -1,10 +1,10 @@ --- -id: authorizing-parameters-steps-and-actions -title: 'Authorizing parameters, steps and actions' -description: How to authorize part of a template +id: authorizing-scaffolder-tasks-parameters-steps-and-actions +title: 'Authorizing scaffolder tasks parameters, steps and actions' +description: How to authorize part of a template and authorize scaffolder task access --- -The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. +The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks. ### Authorizing parameters and steps @@ -174,7 +174,64 @@ class ExamplePermissionPolicy implements PermissionPolicy { } ``` -Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex cases. +### Authorizing scaffolder tasks + +The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to the scaffolder. + +```ts title="packages/src/backend/plugins/permissions.ts" +/* highlight-add-start */ +import { + taskCancelPermission, + taskCreatePermission, + taskReadPermission, +} from '@backstage/plugin-scaffolder-common/alpha'; +/* highlight-add-end */ + +class ExamplePermissionPolicy implements PermissionPolicy { + async handle( + request: PolicyQuery, + user?: BackstageIdentityResponse, + ): Promise { + /* highlight-add-start */ + if (isPermission(request.permission, taskCreatePermission)) { + if (user?.identity.userEntityRef === 'user:default/spiderman') { + return { + result: AuthorizeResult.ALLOW, + }; + } + } + if (isPermission(request.permission, taskCancelPermission)) { + if (user?.identity.userEntityRef === 'user:default/spiderman') { + return { + result: AuthorizeResult.ALLOW, + }; + } + } + if (isPermission(request.permission, taskReadPermission)) { + if (user?.identity.userEntityRef === 'user:default/spiderman') { + return { + result: AuthorizeResult.ALLOW, + }; + } + } + /* highlight-add-end */ + + return { + result: AuthorizeResult.DENY, + }; + } +} +``` + +In the provided example permission policy, we only grant the `spiderman` user permissions to perform/access the following actions/resources: + +- Read all scaffolder tasks and their associated events/logs. +- Cancel any ongoing scaffolder tasks. +- Trigger software templates, which effectively creates new scaffolder tasks. + +Any other user would be denied access to these actions/resources. + +Although the rules exported by the scaffolder are simple, combining them can help you achieve more complex use cases. ### Authorizing in the New Backend System diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 6d9ba3a680..f2fc4afdaa 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -129,7 +129,7 @@ "features/software-templates/writing-tests-for-actions", "features/software-templates/writing-custom-field-extensions", "features/software-templates/writing-custom-step-layouts", - "features/software-templates/authorizing-parameters-steps-and-actions", + "features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions", "features/software-templates/migrating-to-rjsf-v5", "features/software-templates/migrating-from-v1beta2-to-v1beta3" ] From 9a328699b8d6db802ddc2f03388c823d432dea10 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Wed, 8 May 2024 13:44:59 -0400 Subject: [PATCH 319/567] chore: update changesets Signed-off-by: Frank Kong --- .changeset/tender-seas-listen.md | 1 - .changeset/weak-gifts-occur.md | 1 - 2 files changed, 2 deletions(-) diff --git a/.changeset/tender-seas-listen.md b/.changeset/tender-seas-listen.md index cfcea07574..86b0bf4618 100644 --- a/.changeset/tender-seas-listen.md +++ b/.changeset/tender-seas-listen.md @@ -9,4 +9,3 @@ updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend comp - `scaffolder.task.create` - `scaffolder.task.cancel` - `scaffolder.task.read` -- `scaffolder.action.read` diff --git a/.changeset/weak-gifts-occur.md b/.changeset/weak-gifts-occur.md index c1a65e7dff..7834c0a9d9 100644 --- a/.changeset/weak-gifts-occur.md +++ b/.changeset/weak-gifts-occur.md @@ -8,4 +8,3 @@ added the following new permissions to the scaffolder backend endpoints: - `scaffolder.task.create` - `scaffolder.task.cancel` - `scaffolder.task.read` -- `scaffolder.action.read` From 3e8d4ca63ef54ed998467a55d0368b2c609bc2eb Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 8 May 2024 20:15:06 +0200 Subject: [PATCH 320/567] catalog-github: add default namespace support to GithubMultiOrgEntityProvider Signed-off-by: Vincenzo Scamporlino --- .../GithubMultiOrgEntityProvider.test.ts | 318 +++++++++++++++++- .../providers/GithubMultiOrgEntityProvider.ts | 16 +- 2 files changed, 318 insertions(+), 16 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index 0542e64d64..87d197d4ea 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -68,26 +68,24 @@ describe('GithubMultiOrgEntityProvider', () => { headers: { token: 'blah' }, type: 'app', }); - - const githubCredentialsProvider: GithubCredentialsProvider = { - getCredentials: mockGetCredentials, - }; - - entityProvider = new GithubMultiOrgEntityProvider({ - id: 'my-id', - gitHubConfig, - githubCredentialsProvider, - githubUrl: 'https://github.com', - logger, - orgs: ['orgA', 'orgB'], // only include for tests that require it - }); - - entityProvider.connect(entityProviderConnection); }); afterEach(() => jest.resetAllMocks()); it('should read specified orgs', async () => { + entityProvider = new GithubMultiOrgEntityProvider({ + id: 'my-id', + gitHubConfig, + githubCredentialsProvider: { + getCredentials: mockGetCredentials, + }, + githubUrl: 'https://github.com', + logger, + orgs: ['orgA', 'orgB'], + }); + + await entityProvider.connect(entityProviderConnection); + mockClient .mockResolvedValueOnce({ organization: { @@ -348,6 +346,19 @@ describe('GithubMultiOrgEntityProvider', () => { }); it('should read every accessible org', async () => { + entityProvider = new GithubMultiOrgEntityProvider({ + id: 'my-id', + gitHubConfig, + githubCredentialsProvider: { + getCredentials: mockGetCredentials, + }, + githubUrl: 'https://github.com', + logger, + orgs: ['orgA', 'orgB'], + }); + + await entityProvider.connect(entityProviderConnection); + getAllInstallationsMock.mockResolvedValue([ { target_type: 'Organization', @@ -636,7 +647,284 @@ describe('GithubMultiOrgEntityProvider', () => { }); }); + it('should use the default namespace if options.defaultNamespace is provided', async () => { + mockClient + .mockResolvedValueOnce({ + organization: { + membersWithRole: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + login: 'a', + name: 'b', + bio: 'c', + email: 'd', + avatarUrl: 'e', + }, + { + login: 'x', + name: 'y', + bio: 'z', + email: 'w', + avatarUrl: 'v', + }, + ], + }, + }, + }) + .mockResolvedValueOnce({ + organization: { + teams: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + slug: 'team', + combinedSlug: 'orgC/team', + name: 'Team', + description: 'The one and only team', + avatarUrl: 'http://example.com/team.jpeg', + parentTeam: { + slug: 'parent', + combinedSlug: '', + members: { pageInfo: { hasNextPage: false }, nodes: [] }, + }, + members: { + pageInfo: { hasNextPage: false }, + nodes: [{ login: 'a' }, { login: 'x' }], + }, + }, + ], + }, + }, + }) + .mockResolvedValueOnce({ + organization: { + membersWithRole: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + login: 'a', + name: 'b', + bio: 'c', + email: 'd', + avatarUrl: 'e', + }, + { + login: 'x', + name: 'y', + bio: 'z', + email: 'w', + avatarUrl: 'v', + }, + { + login: 'q', + name: 'r', + bio: 's', + email: 't', + avatarUrl: 'u', + }, + ], + }, + }, + }) + .mockResolvedValueOnce({ + organization: { + teams: { + pageInfo: { hasNextPage: false }, + nodes: [ + { + slug: 'team', + combinedSlug: 'orgD/team', + name: 'Team', + description: 'The one and only team', + avatarUrl: 'http://example.com/team.jpeg', + parentTeam: { + slug: 'parent', + combinedSlug: '', + members: { pageInfo: { hasNextPage: false }, nodes: [] }, + }, + members: { + pageInfo: { hasNextPage: false }, + nodes: [{ login: 'a' }, { login: 'q' }], + }, + }, + ], + }, + }, + }); + + (graphql.defaults as jest.Mock).mockReturnValue(mockClient); + + entityProvider = new GithubMultiOrgEntityProvider({ + id: 'my-id', + gitHubConfig, + githubCredentialsProvider: { + getCredentials: mockGetCredentials, + }, + githubUrl: 'https://github.com', + logger, + orgs: ['orgA', 'orgB'], + defaultNamespace: true, + }); + + await entityProvider.connect(entityProviderConnection); + await entityProvider.read(); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + entities: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/a', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/a', + 'github.com/user-login': 'a', + }, + description: 'c', + name: 'a', + }, + spec: { + memberOf: ['team'], + profile: { + displayName: 'b', + email: 'd', + picture: 'e', + }, + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/x', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/x', + 'github.com/user-login': 'x', + }, + description: 'z', + name: 'x', + }, + spec: { + memberOf: ['team'], + profile: { + displayName: 'y', + email: 'w', + picture: 'v', + }, + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/q', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/q', + 'github.com/user-login': 'q', + }, + description: 's', + name: 'q', + }, + spec: { + memberOf: ['team'], + profile: { + displayName: 'r', + email: 't', + picture: 'u', + }, + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/orgs/orgC/teams/team', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/orgs/orgC/teams/team', + 'github.com/team-slug': 'orgC/team', + }, + name: 'team', + description: 'The one and only team', + }, + spec: { + children: [], + parent: 'parent', + profile: { + displayName: 'Team', + picture: 'http://example.com/team.jpeg', + }, + type: 'team', + members: ['default/a', 'default/x'], + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://github.com/orgs/orgD/teams/team', + 'backstage.io/managed-by-origin-location': + 'url:https://github.com/orgs/orgD/teams/team', + 'github.com/team-slug': 'orgD/team', + }, + name: 'team', + description: 'The one and only team', + }, + spec: { + children: [], + parent: 'parent', + profile: { + displayName: 'Team', + picture: 'http://example.com/team.jpeg', + }, + type: 'team', + members: ['default/a', 'default/q'], + }, + }, + locationKey: 'github-multi-org-provider:my-id', + }, + ], + type: 'full', + }); + }); + it('should not call applyMutation if an error is thrown', async () => { + entityProvider = new GithubMultiOrgEntityProvider({ + id: 'my-id', + gitHubConfig, + githubCredentialsProvider: { + getCredentials: mockGetCredentials, + }, + githubUrl: 'https://github.com', + logger, + orgs: ['orgA', 'orgB'], + }); + + await entityProvider.connect(entityProviderConnection); + mockClient.mockImplementationOnce(() => { throw new Error('Network Error'); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index f07d033ecd..bac6383459 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -143,6 +143,15 @@ export interface GithubMultiOrgEntityProviderOptions { */ githubCredentialsProvider?: GithubCredentialsProvider; + /** + * Use the default namespace for groups. By default, groups will be namespaced according to their GitHub org. + * + * @remarks + * + * If set to true, groups with the same name across different orgs will be considered the same group. + */ + defaultNamespace?: boolean; + /** * Optionally include a user transformer for transforming from GitHub users to User Entities */ @@ -198,6 +207,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { userTransformer: options.userTransformer, teamTransformer: options.teamTransformer, events: options.events, + defaultNamespace: options.defaultNamespace, }); provider.schedule(options.schedule); @@ -216,6 +226,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { orgs?: string[]; userTransformer?: UserTransformer; teamTransformer?: TeamTransformer; + defaultNamespace?: boolean; }, ) {} @@ -846,7 +857,10 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const result = await defaultOrganizationTeamTransformer(team, ctx); if (result && result.spec) { - result.metadata.namespace = ctx.org.toLocaleLowerCase('en-US'); + if (!this.options.defaultNamespace) { + result.metadata.namespace = ctx.org.toLocaleLowerCase('en-US'); + } + // Group `spec.members` inherits the namespace of it's group so need to explicitly specify refs here result.spec.members = team.members.map( user => `${DEFAULT_NAMESPACE}/${user.login}`, From 611e089668fcc9c61c834403082d079aa73fdfed Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 8 May 2024 20:24:39 +0200 Subject: [PATCH 321/567] catalog-github: api reports Signed-off-by: Vincenzo Scamporlino --- plugins/catalog-backend-module-github/api-report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index b53d1b7b17..3c7b563593 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -150,6 +150,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { orgs?: string[]; userTransformer?: UserTransformer; teamTransformer?: TeamTransformer; + defaultNamespace?: boolean; }); // (undocumented) connect(connection: EntityProviderConnection): Promise; @@ -165,6 +166,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { // @public export interface GithubMultiOrgEntityProviderOptions { + defaultNamespace?: boolean; events?: EventsService; githubCredentialsProvider?: GithubCredentialsProvider; githubUrl: string; From 5bdeaa7300072bc83e6fb6d6c3b5fe15bd942d8c Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 8 May 2024 20:24:55 +0200 Subject: [PATCH 322/567] github-org: changesets Signed-off-by: Vincenzo Scamporlino --- .changeset/red-mangos-fly.md | 5 +++++ .changeset/wild-cats-hug.md | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 .changeset/red-mangos-fly.md create mode 100644 .changeset/wild-cats-hug.md diff --git a/.changeset/red-mangos-fly.md b/.changeset/red-mangos-fly.md new file mode 100644 index 0000000000..73af978be2 --- /dev/null +++ b/.changeset/red-mangos-fly.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github-org': patch +--- + +Fixed an issue where the `catalog-backend-module-github-org` would not correctly create groups using `default` as namespace in case a single organization was configured. diff --git a/.changeset/wild-cats-hug.md b/.changeset/wild-cats-hug.md new file mode 100644 index 0000000000..180bf50747 --- /dev/null +++ b/.changeset/wild-cats-hug.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Added `defaultNamespace` option to `GithubMultiOrgEntityProvider`. + +If set to true, the provider will use `default` as the namespace for all group entities. Groups with the same name across different orgs will be considered the same group. From 11c9f1f0617a059d4953db2e7c1193095c20f16f Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 8 May 2024 20:25:22 +0200 Subject: [PATCH 323/567] github-org: use default namespace if a single org is provided Signed-off-by: Vincenzo Scamporlino --- plugins/catalog-backend-module-github-org/src/module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index 0baa768644..91e0cff049 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -113,6 +113,7 @@ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ logger, userTransformer, teamTransformer, + defaultNamespace: definition.orgs?.length === 1, }), ); } From 44fd44dea1d758011fabfbb2dc7f69b3022aded5 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Thu, 9 May 2024 00:15:51 +0530 Subject: [PATCH 324/567] line prop is back Signed-off-by: npiyush97 --- .../OverflowTooltip/OverflowTooltip.stories.tsx | 8 ++++++-- .../src/components/OverflowTooltip/OverflowTooltip.tsx | 9 +++++---- .../catalog-react/src/components/EntityTable/columns.tsx | 1 + plugins/catalog/src/components/CatalogTable/columns.tsx | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx index a302a0a7e3..5ab5ba7dc4 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.stories.tsx @@ -33,12 +33,16 @@ export const Default = () => ( export const MultiLine = () => ( - + ); export const DifferentTitle = () => ( - + ); diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index 12de79644f..ab45d87d33 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ /* * Copyright 2020 The Backstage Authors * @@ -23,6 +22,7 @@ import Typography from '@material-ui/core/Typography'; type Props = { text?: string | undefined; title?: TooltipProps['title']; + line?: number | undefined; placement?: TooltipProps['placement']; }; @@ -35,17 +35,18 @@ const useStyles = makeStyles( }, typo: { maxWidth: 200, - display: 'inline-block', overflow: 'hidden', - whiteSpace: 'nowrap', textOverflow: 'ellipsis', + display: '-webkit-box', + '-webkit-line-clamp': ({ line }: Props) => line, + '-webkit-box-orient': 'vertical', }, }, { name: 'BackstageOverflowTooltip' }, ); export function OverflowTooltip(props: Props) { - const classes = useStyles(); + const classes = useStyles(props); return ( ), }; diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index d5362badda..5d2f9146d9 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -141,6 +141,7 @@ export const columnFactories = Object.freeze({ ), width: 'auto', From 39564b326597080950e3606d40359fb265bb9d24 Mon Sep 17 00:00:00 2001 From: David Weber Date: Mon, 11 Mar 2024 23:08:36 +0100 Subject: [PATCH 325/567] feat: allow multiple edges with different type Signed-off-by: David Weber --- .changeset/tame-jokes-bow.md | 5 ++ .../DefaultRenderLabel.tsx | 2 +- .../useEntityRelationNodesAndEdges.test.ts | 24 --------- .../useEntityRelationNodesAndEdges.ts | 51 ++++++++++++++++++- 4 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 .changeset/tame-jokes-bow.md diff --git a/.changeset/tame-jokes-bow.md b/.changeset/tame-jokes-bow.md new file mode 100644 index 0000000000..e08cf9ca0c --- /dev/null +++ b/.changeset/tame-jokes-bow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Allow multiple edges with different type (e.g. `ownedBy` and `applicationOwnerBy`) to have the same source and target node. diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderLabel.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderLabel.tsx index cde4808f6e..ae01a113dd 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderLabel.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderLabel.tsx @@ -38,7 +38,7 @@ export function DefaultRenderLabel({ return ( {relations.map((r, i) => ( - 0 && classes.secondary)}> + {i > 0 && / } {r} diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts index 8c7b59b9c4..0a5e43bf37 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts @@ -371,12 +371,6 @@ describe('useEntityRelationNodesAndEdges', () => { relations: [RELATION_HAS_PART, RELATION_PART_OF], to: 'b:d/c1', }, - { - from: 'b:d/c', - label: 'visible', - relations: [RELATION_HAS_PART, RELATION_PART_OF], - to: 'b:d/c1', - }, { from: 'b:d/c1', label: 'visible', @@ -389,24 +383,6 @@ describe('useEntityRelationNodesAndEdges', () => { relations: [RELATION_HAS_PART, RELATION_PART_OF], to: 'b:d/c2', }, - { - from: 'b:d/c1', - label: 'visible', - relations: [RELATION_HAS_PART, RELATION_PART_OF], - to: 'b:d/c2', - }, - { - from: 'b:d/c', - label: 'visible', - relations: [RELATION_OWNER_OF, RELATION_OWNED_BY], - to: 'k:d/a1', - }, - { - from: 'b:d/c1', - label: 'visible', - relations: [RELATION_OWNER_OF, RELATION_OWNED_BY], - to: 'k:d/a1', - }, ]); }); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts index d2a5138ccb..74aff1b3d0 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts @@ -146,11 +146,60 @@ export function useEntityRelationNodesAndEdges({ nodeQueue.push(rel.targetRef); visitedNodes.add(rel.targetRef); } + + // if unidirectional add missing relations as entities are only visited once + if (unidirectional) { + const findIndex = edges.findIndex( + edge => + entityRef === edge.from && + rel.targetRef === edge.to && + !edge.relations.includes(rel.type), + ); + if (findIndex >= 0) { + if (mergeRelations) { + const pair = relationPairs.find( + ([l, r]) => l === rel.type || r === rel.type, + ) ?? [rel.type]; + edges[findIndex].relations = [ + ...edges[findIndex].relations, + ...pair, + ]; + } else { + edges[findIndex].relations = [ + ...edges[findIndex].relations, + rel.type, + ]; + } + } + } }); } } - setNodesAndEdges({ nodes, edges }); + // Reduce edges as the dependency graph anyway ignores duplicated edges regarding from / to + // Additionally, this will improve rendering speed for the dependency graph + const finalEdges = edges.reduce((previousEdges, currentEdge) => { + const indexFound = previousEdges.findIndex( + previousEdge => + previousEdge.from === currentEdge.from && + previousEdge.to === currentEdge.to, + ); + if (indexFound >= 0) { + previousEdges[indexFound] = { + ...previousEdges[indexFound], + relations: Array.from( + new Set([ + ...previousEdges[indexFound].relations, + ...currentEdge.relations, + ]), + ), + }; + return previousEdges; + } + return [...previousEdges, currentEdge]; + }, [] as EntityEdge[]); + + setNodesAndEdges({ nodes, edges: finalEdges }); }, 100, [ From 1bed9a35c02ac7c215f905dc0bfc358a667285e8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 9 May 2024 09:50:12 +0200 Subject: [PATCH 326/567] core-app-api: remove fallback to provider expiration Signed-off-by: Patrik Oldsberg --- .changeset/slimy-donkeys-laugh.md | 5 +++++ .../src/apis/implementations/auth/oauth2/OAuth2.ts | 9 ++------- 2 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 .changeset/slimy-donkeys-laugh.md diff --git a/.changeset/slimy-donkeys-laugh.md b/.changeset/slimy-donkeys-laugh.md new file mode 100644 index 0000000000..c18102e68c --- /dev/null +++ b/.changeset/slimy-donkeys-laugh.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +The Backstage identity session expiration check will no longer fall back to using the provider expiration. This was introduced to smooth out the rollout of Backstage release 1.18, and is no longer needed. diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 6be7cfc89d..dbad031652 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -116,16 +116,11 @@ export default class OAuth2 }, }; if (backstageIdentity) { - // TODO(Rugvip): This fallback can be removed a few releases after 1.18. It's there to avoid - // breaking deployments that update their frontend before updating their backend. - const expInSec = - backstageIdentity.expiresInSeconds ?? - res.providerInfo.expiresInSeconds; session.backstageIdentity = { token: backstageIdentity.token, identity: backstageIdentity.identity, - expiresAt: expInSec - ? new Date(Date.now() + expInSec * 1000) + expiresAt: backstageIdentity.expiresInSeconds + ? new Date(Date.now() + backstageIdentity.expiresInSeconds * 1000) : undefined, }; } From 507185c1f2e6286e14de9537fada65d69e412b3f Mon Sep 17 00:00:00 2001 From: Marley Date: Thu, 9 May 2024 10:40:28 +0100 Subject: [PATCH 327/567] Fixed typo in 05-extension-overrides.md Signed-off-by: Marley --- docs/frontend-system/architecture/05-extension-overrides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/05-extension-overrides.md b/docs/frontend-system/architecture/05-extension-overrides.md index 21028627bc..63a2e58716 100644 --- a/docs/frontend-system/architecture/05-extension-overrides.md +++ b/docs/frontend-system/architecture/05-extension-overrides.md @@ -108,7 +108,7 @@ const customSearchPage = createPageExtension({ loader: () => import('./SearchPage').then(m => m.), }); -export createExtensionOverrides({ +export default createExtensionOverrides({ extensions: [customSearchPage] }); ``` From 48086b590f51f5081a9ec5fdcab755a2f0cf9382 Mon Sep 17 00:00:00 2001 From: AmbrishRamachandiran Date: Thu, 9 May 2024 15:14:38 +0530 Subject: [PATCH 328/567] Updated document of resource permission check Signed-off-by: AmbrishRamachandiran --- .../plugin-authors/03-adding-a-resource-permission-check.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index 76c1a85e24..b3aeb13a68 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -144,7 +144,11 @@ export const rules = { isOwner }; `makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query. -> Note: To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. +:::note Note + +To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. + +::: We have created a new `isOwner` rule, which is going to be automatically used by the permission framework whenever a conditional response is returned in response to an authorized request with an attached `resourceRef`. Specifically, the `apply` function is used to understand whether the passed resource should be authorized or not. From 905d18b50f1313384efb836f51a8b03a0fa35ab3 Mon Sep 17 00:00:00 2001 From: Marley Date: Thu, 9 May 2024 12:46:46 +0100 Subject: [PATCH 329/567] Fixed typo in 05-extension-overrides.md Signed-off-by: Marley --- docs/frontend-system/architecture/05-extension-overrides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/05-extension-overrides.md b/docs/frontend-system/architecture/05-extension-overrides.md index 63a2e58716..f9bd5edd82 100644 --- a/docs/frontend-system/architecture/05-extension-overrides.md +++ b/docs/frontend-system/architecture/05-extension-overrides.md @@ -59,7 +59,7 @@ const apertureDarkTheme = createThemeExtension({ }); // Creating an extension overrides preset -export createExtensionOverrides({ +export default createExtensionOverrides({ extensions: [apertureLightTheme, apertureDarkTheme] }); ``` From dce858ac36dc493b2009d31dba976d0334c56bb9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 9 May 2024 13:48:48 +0200 Subject: [PATCH 330/567] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Patrik Oldsberg --- docs/overview/threat-model.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index a2f4b4a1df..fcac9c8de5 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -53,7 +53,7 @@ Backstage provides authentication of users through the `auth` plugin, which prim In order to use an auth provider to sign in users into Backstage, it needs to be configured with an [Identity resolver](https://backstage.io/docs/auth/identity-resolver), which is a custom callback implemented in code. The identity resolver is a sensitive part of configuring Backstage and it is important that it always resolves user identities correctly, based on information provided by the authentication provider. There are a number of built-in identity resolvers that can simplify configuration, and it is important that these all resolve users in a secure way, regardless of how they are used. -Backstage also supports authentication through a proxy where the user identity is read from the incoming request from the proxy, which has been decorated by an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/). The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` does not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. It’s therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider. +Backstage also supports authentication through an authenticating reverse proxy such as [AWS ALB](https://aws.amazon.com/elasticloadbalancing/application-load-balancer/), where the user identity is read from the incoming proxied decorated request. The following proxy auth providers verify the signature of incoming requests, and are therefore safe to deploy with direct access by users: `awsAlb`, `cfAccess`, and `gcpIap`. Providers like `oauth2Proxy` do not verify the incoming request and can therefore be spoofed by a malicious internal user to supply the `auth` backend with forged identity information. It’s therefore highly recommended to restrict access to the `oauth2Proxy` endpoints, or use a different provider. As part of signing in with an identity resolver, a Backstage Token is issued containing the resolved user identity. The tokens are asymmetrically signed JSON Web Tokens, with the public keys available to any service that wishes to verify a token. The signing keys are rotated continuously and are unique to each installation of Backstage, meaning that Backstage Tokens are not shared across installations. The token contains claims for the user identity and ownership information, which can be used to determine what Backstage resources are owned by that user or group. It is important that this token can not be forged outside of the `auth` plugin, with the exception of other plugins deployed in the same backend service or sharing the same database. For a high-security deployment, the `auth` backend should therefore be deployed in a separate service with its own database. @@ -63,7 +63,7 @@ One of the claims in a user token is the User Identity Proof or `uip`. This is a The communication across backend plugins uses a similar authentication scheme to the user authentication. Each backend plugin generates and publishes its own set of keys that it uses to sign its tokens, and the public keys are shared with all other plugins for verification. The expected location of each plugin's published JWKS is determined by the `DiscoveryService` implementation in the backend, which means that it is vital for any custom implementation of that service to be careful with user input. The tokens signed by each plugin contain both the source and target plugin ID, which means that the token can not be reused to access other plugins. -When forwarding a user identity in a call across backend plugins only the limited user token with `uip` is used, wrapped in a new plugin token that is signed by the calling plugin. This means that the receiving plugin can trust the user identity, but it is not able to make further calls on behalf of the user except for with the plugins that it is authorized to call. That is except for any endpoints in other plugins that accept limited user tokens, which is a reason to avoid accepting them when possible. +When forwarding a user identity in a call across backend plugins only the limited user token with `uip` is used, wrapped in a new service token that is signed by the calling plugin. This means that the receiving plugin can trust the user identity, but it is not able to make further calls on behalf of the user except for with the plugins that it is authorized to call. That is except for any endpoints in other plugins that accept limited user tokens, which is a reason to avoid accepting them when possible. ## Catalog From b6582bc2cf17dbb96df8328089044ebb9fba4b59 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 9 May 2024 13:57:24 +0200 Subject: [PATCH 331/567] docs/threat-model: more explicit builder persona Signed-off-by: Patrik Oldsberg --- docs/overview/threat-model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index fcac9c8de5..da97f3fc83 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -16,7 +16,7 @@ An **internal user** is an authenticated user that generally belongs to the orga An **operator** is a user responsible for configuring and maintaining an instance of Backstage. Operators are fully trusted, since they operate the system and database and therefore have root access to the host system. Additional measures can be taken by adopters of Backstage in order to restrict or observe the access of this group, but that falls outside of the current scope of Backstage. -Another group of de facto integrators is internal and external code contributors. When installing Backstage plugins you should vet them just like any other package from an external source. While it’s possible to limit the impact of for example a supply chain attack by splitting the deployment into separate services with different plugins, the Backstage project itself does not aim to prevent these kinds of attacks or in any other way sandbox or limit the access of plugins. +A **builder** is an internal or external code contributor and end up having a similar level of access as operators. When installing Backstage plugins you should vet them just like any other package from an external source. While it’s possible to limit the impact of for example a supply chain attack by splitting the deployment into separate services with different plugins, the Backstage project itself does not aim to prevent these kinds of attacks or in any other way sandbox or limit the access of plugins. An **external user** is a user that does not belong to the other two groups, for example a malicious actor outside of the organization. The security model of Backstage currently assumes that this group does not have any direct access to Backstage, and it is the responsibility of each adopter of Backstage to make sure this is the case. From 786c1dec28c615dc347ba2e5f8fd96eb3d52e013 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Thu, 9 May 2024 19:30:49 +0530 Subject: [PATCH 332/567] clamp 1 by default no need for whitespace Signed-off-by: npiyush97 --- .../src/components/OverflowTooltip/OverflowTooltip.tsx | 2 +- plugins/catalog-react/src/components/EntityTable/columns.tsx | 2 +- plugins/catalog/src/components/CatalogTable/columns.tsx | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index ab45d87d33..f0799b0d69 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -38,7 +38,7 @@ const useStyles = makeStyles( overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', - '-webkit-line-clamp': ({ line }: Props) => line, + '-webkit-line-clamp': ({ line }: Props) => line || 1, '-webkit-box-orient': 'vertical', }, }, diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index e98c603d71..292235c78c 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -141,7 +141,7 @@ export const columnFactories = Object.freeze({ ), }; diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 5d2f9146d9..d5362badda 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -141,7 +141,6 @@ export const columnFactories = Object.freeze({ ), width: 'auto', From 94e53c6331251ab2b3c7170b47a8d678c7e4a66e Mon Sep 17 00:00:00 2001 From: Kieran Lea Date: Thu, 9 May 2024 20:26:25 -0400 Subject: [PATCH 333/567] Fixed import typo in example in SECURITY.md Signed-off-by: Kieran Lea --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index 5866fff2b4..8de1307356 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -66,7 +66,7 @@ The insecure example above should instead be written like this: ```ts // THIS IS GOOD, DO THIS -import { resolveSafeChildPath } from '@backstaghe/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-common'; import fs from 'fs-extra'; function writeTemporaryFile(tmpDir: string, name: string, content: string) { From 71f18a999eb4cef13167b45991d9bf9117902c50 Mon Sep 17 00:00:00 2001 From: NIKUNJ LALITKUMAR HUDKA Date: Thu, 9 May 2024 23:46:02 -0300 Subject: [PATCH 334/567] chore: use promise getter of entityPresentationApi Signed-off-by: NIKUNJ LALITKUMAR HUDKA --- .../MultiEntityPicker/MultiEntityPicker.tsx | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index d2ad90aa10..f8fc831976 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -27,6 +27,7 @@ import { catalogApiRef, entityPresentationApiRef, EntityDisplayName, + EntityRefPresentationSnapshot, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; import FormControl from '@material-ui/core/FormControl'; @@ -70,8 +71,22 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { const { items } = await catalogApi.getEntities( catalogFilter ? { filter: catalogFilter } : undefined, ); - - return items; + const entityRefToPresentation = new Map< + string, + EntityRefPresentationSnapshot + >( + await Promise.all( + items.map(async item => { + const presentation = await entityPresentationApi.forEntity(item) + .promise; + return [stringifyEntityRef(item), presentation] as [ + string, + EntityRefPresentationSnapshot, + ]; + }), + ), + ); + return { entities: items, entityRefToPresentation }; }); const allowArbitraryValues = uiSchema['ui:options']?.allowArbitraryValues ?? true; @@ -115,8 +130,8 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { ); useEffect(() => { - if (entities?.length === 1) { - onChange([stringifyEntityRef(entities[0])]); + if (entities?.entities?.length === 1) { + onChange([stringifyEntityRef(entities?.entities[0])]); } }, [entities, onChange]); @@ -129,20 +144,18 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { } getOptionLabel={option => // option can be a string due to freeSolo. typeof option === 'string' ? option - : entityPresentationApi.forEntity(option, { - defaultKind, - defaultNamespace, - }).snapshot.entityRef! + : entities?.entityRefToPresentation.get(stringifyEntityRef(option)) + ?.entityRef! } autoSelect freeSolo={allowArbitraryValues} From 628ba69f569b7c7abe3854330bafc067016e0232 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Fri, 10 May 2024 07:18:55 +0000 Subject: [PATCH 335/567] prettier:fix Signed-off-by: Marley Powell --- docs/frontend-system/architecture/05-extension-overrides.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/frontend-system/architecture/05-extension-overrides.md b/docs/frontend-system/architecture/05-extension-overrides.md index f9bd5edd82..cdd1bd8058 100644 --- a/docs/frontend-system/architecture/05-extension-overrides.md +++ b/docs/frontend-system/architecture/05-extension-overrides.md @@ -25,7 +25,7 @@ In the example below, we create a file that exports custom extensions for the ap ```tsx title="packages/app/src/themes.ts" import { createThemeExtension, - createExtensionOverrides + createExtensionOverrides, } from '@backstage/frontend-plugin-api'; import { apertureThemes } from './themes'; import { ApertureLightIcon, ApertureDarkIcon } from './icons'; @@ -60,7 +60,7 @@ const apertureDarkTheme = createThemeExtension({ // Creating an extension overrides preset export default createExtensionOverrides({ - extensions: [apertureLightTheme, apertureDarkTheme] + extensions: [apertureLightTheme, apertureDarkTheme], }); ``` From d618403e7f05d44dfde0d6789588862a1213e528 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Fri, 10 May 2024 07:28:18 +0000 Subject: [PATCH 336/567] improved docs Signed-off-by: Marley Powell --- .../architecture/05-extension-overrides.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/frontend-system/architecture/05-extension-overrides.md b/docs/frontend-system/architecture/05-extension-overrides.md index cdd1bd8058..41702e6823 100644 --- a/docs/frontend-system/architecture/05-extension-overrides.md +++ b/docs/frontend-system/architecture/05-extension-overrides.md @@ -22,7 +22,7 @@ In order to override an app extension, you must create a new extension and add i In the example below, we create a file that exports custom extensions for the app's `light` and `dark` themes: -```tsx title="packages/app/src/themes.ts" +```tsx title="packages/app/src/themes.tsx" import { createThemeExtension, createExtensionOverrides, @@ -96,8 +96,11 @@ We recommend that plugin developers share the extension IDs in their plugin docu Imagine you have a plugin with the ID `'search'`, and the plugin provides a page extension that you want to fully override with your own custom component. To do so, you need to create your page extension with an explicit `namespace` option that matches that of the plugin that you want to override, in this case `'search'`. If the existing extension also has an explicit `name` you'd need to set the `name` of your override extension to the same value as well. -```tsx title="packages/app/src/search.ts" -import { createPageExtension } from '@backstage/frontend-plugin-api'; +```tsx title="packages/app/src/search.tsx" +import { + createPageExtension, + createExtensionOverrides, +} from '@backstage/frontend-plugin-api'; // Creating a custom search page extension const customSearchPage = createPageExtension({ @@ -137,7 +140,7 @@ Sometimes you just need to quickly create a new extension and not overwrite an a Imagine you want to create a page that is currently only used by your application, like an Institutional page, for example. You can use overrides to extend the Backstage app to render it. To do so, simply create a page extension and pass it to the app as an override: -```tsx title="packages/app/src/App.ts" +```tsx title="packages/app/src/App.tsx" import { createApp } from '@backstage/frontend-app-api'; import { createPageExtension, From b8d12d8c5ec65338ae873b790d70436ab3605f17 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Fri, 10 May 2024 13:00:50 +0530 Subject: [PATCH 337/567] Updated the accessibility document Signed-off-by: Aditya Kumar --- docs/accessibility/index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/accessibility/index.md b/docs/accessibility/index.md index b4d63fd4e4..da10d60dd2 100644 --- a/docs/accessibility/index.md +++ b/docs/accessibility/index.md @@ -47,7 +47,10 @@ If you want to use the Lighthouse CLI and run the checks based on the config you yarn dlx @lhci/cli@0.11.x autorun ``` -> Note: running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed. +:::note Note +Running this command will use the [Lighthouse config](https://github.com/backstage/backstage/blob/39ba2284d73885b7ca8290cb38e2b1e4d983c8d6/lighthouserc.js#L19-L34) so make sure to adjust it to your needs if needed. + +::: ### Use Lighthouse Github Action on your own repo From 5559acab7c71089447f9c54b22568228e174eff1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 10 May 2024 09:52:37 +0200 Subject: [PATCH 338/567] scripts/verify-links: add check for multi-line links Signed-off-by: Patrik Oldsberg --- scripts/verify-links.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/verify-links.js b/scripts/verify-links.js index 94ffc497cd..26d31ce6f0 100755 --- a/scripts/verify-links.js +++ b/scripts/verify-links.js @@ -137,6 +137,15 @@ async function verifyFile(filePath, docPages) { } } + const multiLineLinks = content.match(/\[[^\]\n]+?\n[^\]\n]*?\]\(/g) || []; + badUrls.push( + ...multiLineLinks.map(url => ({ + url, + basePath: filePath, + problem: 'multi-line', + })), + ); + return badUrls; } @@ -226,7 +235,7 @@ async function main() { console.error(` From: ${basePath}`); console.error(` To: ${url}`); if (suggestion) { - console.error(` Replace With: ${suggestion}`); + console.error(` Replace with: ${suggestion}`); } } else if (problem === 'not-relative') { console.error('Links within /docs/ must be relative'); @@ -238,6 +247,10 @@ async function main() { ); console.error(` From: ${basePath}`); console.error(` To: ${url}`); + } else if (problem === 'multi-line') { + console.error(`Links are not allowed to span multiple lines:`); + console.error(` From: ${basePath}`); + console.error(` To: ${url.replace(/\n/g, '\n ')}`); } } process.exit(1); From b920a47976e2b2d28dd286fa54e733239059b11f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 10 May 2024 10:16:00 +0200 Subject: [PATCH 339/567] fix multi-line links Signed-off-by: Patrik Oldsberg --- .../tutorials/authenticate-api-requests.md | 3 +- contrib/docs/tutorials/aws-deployment.md | 30 +++++++------------ docs/auth/cloudflare/access.md | 3 +- docs/auth/identity-resolver.md | 15 ++++------ docs/auth/microsoft/provider.md | 3 +- docs/auth/service-to-service-auth.md | 3 +- docs/auth/vmware-cloud/provider.md | 12 +++----- .../building-plugins-and-modules/01-index.md | 13 +++----- .../02-testing.md | 3 +- docs/features/kubernetes/configuration.md | 9 ++---- docs/features/kubernetes/proxy.md | 18 ++++------- docs/features/software-catalog/index.md | 3 +- docs/integrations/github/org--old.md | 3 +- docs/overview/roadmap.md | 6 ++-- docs/plugins/analytics.md | 3 +- docs/releases/v1.12.0-changelog.md | 3 +- docs/releases/v1.12.0-next.2-changelog.md | 3 +- docs/releases/v1.15.0.md | 15 ++++------ docs/releases/v1.3.0-changelog.md | 3 +- docs/releases/v1.7.0-changelog.md | 3 +- docs/releases/v1.7.0-next.0-changelog.md | 3 +- docs/releases/v1.7.0.md | 22 +++++--------- packages/core-app-api/CHANGELOG.md | 6 ++-- packages/create-app/CHANGELOG.md | 12 +++----- plugins/api-docs/CHANGELOG.md | 3 +- plugins/catalog-backend/CHANGELOG.md | 6 ++-- plugins/catalog-backend/README.md | 15 ++++------ plugins/catalog-react/CHANGELOG.md | 3 +- plugins/catalog/CHANGELOG.md | 3 +- plugins/catalog/README.md | 3 +- plugins/scaffolder-backend/README.md | 16 ++++------ plugins/scaffolder-common/CHANGELOG.md | 3 +- plugins/scaffolder/README.md | 3 +- plugins/user-settings/README.md | 3 +- 34 files changed, 84 insertions(+), 171 deletions(-) diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index 838aaffef6..8c858bea0f 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -3,8 +3,7 @@ > [!CAUTION] > This entire guide MUST NOT BE USED by users of Backstage 1.26 and > newer. If you have applied the changes in this guide, you need to remove them -> again as you upgrade to recent versions of Backstage. When [the new auth -> changes](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution) +> again as you upgrade to recent versions of Backstage. When [the new auth changes](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution) > landed backends became natively secured through the framework, and the > instructions outlined in here can interfere with the backend functioning > correctly. diff --git a/contrib/docs/tutorials/aws-deployment.md b/contrib/docs/tutorials/aws-deployment.md index 6edbb73f9d..d43d8aefb0 100644 --- a/contrib/docs/tutorials/aws-deployment.md +++ b/contrib/docs/tutorials/aws-deployment.md @@ -1,11 +1,9 @@ # Deploying Backstage on AWS using ECR and EKS -Backstage documentation shows how to build a [Docker -image](https://backstage.io/docs/deployment/docker); this +Backstage documentation shows how to build a [Docker image](https://backstage.io/docs/deployment/docker); this tutorial shows how to deploy that Docker image to AWS using Elastic Container Registry (ECR) and Elastic Kubernetes Service (EKS). Amazon also supports -deployments with Helm, covered in the [Helm -Kubernetes](../../kubernetes/basic_kubernetes_example_with_helm) example. +deployments with Helm, covered in the [Helm Kubernetes](../../kubernetes/basic_kubernetes_example_with_helm) example. The basic workflow for this method is to build a Backstage Docker image, upload the new version to a container registry, and update a Kubernetes deployment with @@ -13,8 +11,7 @@ the new image. ## Create a container registry -To create an Elastic Container Registry on AWS, go to the [AWS ECR -console](https://console.aws.amazon.com/ecr/repositories). +To create an Elastic Container Registry on AWS, go to the [AWS ECR console](https://console.aws.amazon.com/ecr/repositories). Click `Create repository` and give the repository a name, like `backstage`. @@ -35,8 +32,7 @@ Go to [AWS IAM console](https://console.aws.amazon.com/iam/home) and select ## Publish a Backstage build -Follow the [Docker -image](https://backstage.io/docs/deployment/docker) +Follow the [Docker image](https://backstage.io/docs/deployment/docker) documentation to build a new Backstage Docker image: ```shell @@ -56,8 +52,7 @@ Default region name: Now you can use the AWS CLI to push the built image to the ECR repository. It's a good practice to use a specific version tag as well as `latest` when pushing; -more about [Semver tagging -here](https://medium.com/@mccode/using-semantic-versioning-for-docker-image-tags-dfde8be06699). +more about [Semver tagging here](https://medium.com/@mccode/using-semantic-versioning-for-docker-image-tags-dfde8be06699). Go to the [AWS ECR console](https://console.aws.amazon.com/ecr/repositories) and click on your repository, then `View push commands`. This will show the @@ -83,12 +78,10 @@ Kubernetes deployment based on this image. Creating an Elastic Kubernetes Service (EKS) cluster is beyond the scope of this document, but it can be as easy as `eksctl create cluster` documented in the -[AWS EKS getting started -guide](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html), +[AWS EKS getting started guide](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html), which uses a Cloudformation template to create the necessary resources. -To deploy the Docker image to EKS, follow the [Kubernetes -guide](https://backstage.io/docs/deployment/k8s#creating-the-backstage-instance) +To deploy the Docker image to EKS, follow the [Kubernetes guide](https://backstage.io/docs/deployment/k8s#creating-the-backstage-instance) but set the Backstage deployment `image` to the ECR repository URL: ```yaml @@ -110,8 +103,7 @@ spec: ... ``` -Create the [Service -descriptor](https://backstage.io/docs/deployment/k8s#creating-a-backstage-service) +Create the [Service descriptor](https://backstage.io/docs/deployment/k8s#creating-a-backstage-service) as well, and apply these Kubernetes definitions to the EKS cluster to complete the Backstage deployment: @@ -120,16 +112,14 @@ $ kubectl apply -f kubernetes/backstage.yaml $ kubectl apply -f kubernetes/backstage-service.yaml ``` -Now you can see your Backstage workload running from the [EKS -console](https://console.aws.amazon.com/eks/home). +Now you can see your Backstage workload running from the [EKS console](https://console.aws.amazon.com/eks/home). ## Further steps ### Exposing Backstage with a load balancer To make the service useful, we need to expose the workload with a load balancer. -Follow the [Application load balancing on -EKS](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html) guide to +Follow the [Application load balancing on EKS](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html) guide to set up a Load Balancer controller and Kubernetes ingress to your application. This is ultimately a `kubectl apply` with an ingress definition: diff --git a/docs/auth/cloudflare/access.md b/docs/auth/cloudflare/access.md index 9df816c39f..52d9fdfc21 100644 --- a/docs/auth/cloudflare/access.md +++ b/docs/auth/cloudflare/access.md @@ -170,6 +170,5 @@ backend.add(customAuth); The body of the sign-in resolver is up to you to write! The example code above is just a copy of what `emailMatchingUserEntityProfileEmail` does. The `info` parameter contains all of the results of the sign-in attempt so far. The `ctx` -context [has several useful -functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/) +context [has several useful functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/) for issuing tokens in various ways. diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 406f789630..4140276df8 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -5,8 +5,7 @@ description: An introduction to Backstage user identities and sign-in resolvers --- :::info -This documentation is written for [the new backend -system](../backend-system/index.md) which is the default since Backstage +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./identity-resolver--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! @@ -31,15 +30,13 @@ testing purposes and quickly getting started locally, but is not safe for use in production and that particular provider will refuse to work there. Because of this, one of the early things you want to do when standing up your -Backstage instance is to choose a production ready auth provider. See [the auth -overview page](./index.md) for a full list of providers and how to install and +Backstage instance is to choose a production ready auth provider. See [the auth overview page](./index.md) for a full list of providers and how to install and configure them. ## Backstage User Identity A user identity within Backstage is built up from two main pieces of -information: a user [entity -reference](../features/software-catalog/references.md), and a set of ownership +information: a user [entity reference](../features/software-catalog/references.md), and a set of ownership references. When a user signs in, a Backstage token is generated which is then used to identify the user within the Backstage ecosystem. @@ -194,8 +191,7 @@ backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); backend.add(customAuth); ``` -Check out [the naming patterns -article](../backend-system/architecture/07-naming-patterns.md) for what rules +Check out [the naming patterns article](../backend-system/architecture/07-naming-patterns.md) for what rules apply regarding how to form valid IDs. In this example we also put the module declaration directly in `packages/backend/src/index.ts` but that's just for simplicity. You can place it anywhere you like, including in other packages, and @@ -244,8 +240,7 @@ async signInResolver(info, ctx) { If you throw an error in the sign in resolver function, the sign in attempt is immediately rejected, and the error details are presented in the user interface. -The `ctx` context [has several useful -functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/) +The `ctx` context [has several useful functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/) for issuing tokens in various ways. ### Custom Ownership Resolution diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index 5c4707c151..ab3c5d8044 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -103,6 +103,5 @@ hosts: - `login.microsoftonline.com`, to get and exchange authorization codes and access tokens - `graph.microsoft.com`, to fetch user profile information (as seen - in [this source - code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)). + in [this source code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)). If this host is unreachable, users may see an `Authentication failed, failed to fetch user profile` error when they attempt to log in. diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index c1d6643769..c08411aca5 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -6,8 +6,7 @@ description: This section describes service to service authentication works, bot --- :::info -This documentation is written for [the new backend -system](../backend-system/index.md) which is the default since Backstage +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./service-to-service-auth--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! diff --git a/docs/auth/vmware-cloud/provider.md b/docs/auth/vmware-cloud/provider.md index 0ff928ab7f..d7580eccba 100644 --- a/docs/auth/vmware-cloud/provider.md +++ b/docs/auth/vmware-cloud/provider.md @@ -12,14 +12,11 @@ Cloud Console and within a Backstage app required to enable this capability. ## Create an OAuth App in the VMware Cloud Console 1. Log in to the [VMware Cloud Console](https://console.cloud.vmware.com). -1. Navigate to [Identity & Access Management > OAuth - Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps) - and click the [Owned - Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps/owned-apps/view) +1. Navigate to [Identity & Access Management > OAuth Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps) + and click the [Owned Apps](https://console.cloud.vmware.com/csp/gateway/portal/#/consumer/usermgmt/oauth-apps/owned-apps/view) tab -- if you are not an Organization Owner or Administrator but only a Member, you will not see this nav entry unless the **Developer** check box is - selected for your role (see the [Organization roles and - permissions](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-C11D3AAC-267C-4F16-A0E3-3EDF286EBE53.html#organization-roles-and-permissions-0) + selected for your role (see the [Organization roles and permissions](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-C11D3AAC-267C-4F16-A0E3-3EDF286EBE53.html#organization-roles-and-permissions-0) docs for details). 1. Click **Create App**, choose 'Web/Mobile app' and click **Continue**. 1. Use default settings except: @@ -161,8 +158,7 @@ auth: ``` Where `APP_ID` refers to the ID retrieved when creating the OAuth App, and -`ORG_ID` is the [long ID of the -Organization](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html#view-the-organization-id-1) +`ORG_ID` is the [long ID of the Organization](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html#view-the-organization-id-1) in VMware Cloud for which you wish to enable sign-in. Note that VMware Cloud requires OAuth Apps to use diff --git a/docs/backend-system/building-plugins-and-modules/01-index.md b/docs/backend-system/building-plugins-and-modules/01-index.md index 168646afab..1aa8b140ed 100644 --- a/docs/backend-system/building-plugins-and-modules/01-index.md +++ b/docs/backend-system/building-plugins-and-modules/01-index.md @@ -73,8 +73,7 @@ items. Backend modules are used to extend [plugins](../architecture/04-plugins.md) or other modules with additional features or change existing behavior. They must always be installed in the same backend instance as the plugin or module that they extend, and may only extend a single plugin and modules from that plugin at a time. -Modules interact with their target plugin or module using the [extension -points](../architecture/05-extension-points.md) registered by the plugin, while also being +Modules interact with their target plugin or module using the [extension points](../architecture/05-extension-points.md) registered by the plugin, while also being able to depend on the [services](../architecture/03-services.md) of the target plugin. That last point is worth reiterating: injected `plugin` scoped services will be the exact @@ -157,8 +156,7 @@ the database. They will run on the same logical database instance as the target plugin, so care must be taken to choose table names that do not risk colliding with those of the plugin. A recommended naming pattern is `__`, for example the `@backstage/backend-tasks` package creates -tables named `backstage_backend_tasks__
    `. If you use the default [`Knex` -migration facilities](https://knexjs.org/guide/migrations.html), you will also +tables named `backstage_backend_tasks__
    `. If you use the default [`Knex` migration facilities](https://knexjs.org/guide/migrations.html), you will also want to make sure that it uses similarly prefixed migration state tables for its internal bookkeeping needs, so they do not collide with the main ones used by the plugin itself. You can do this as follows: @@ -179,8 +177,7 @@ There are several ways of configuring and customizing plugins and modules. Whenever you want to allow modules to configure your plugin dynamically, for example in the way that the catalog backend lets catalog modules inject additional entity providers, you can use the extension points mechanism. This is -described in detail with code examples in [the extension points architecture -article](../architecture/05-extension-points.md), while the following is a more +described in detail with code examples in [the extension points architecture article](../architecture/05-extension-points.md), while the following is a more slim example of how to implement an extension point for a plugin: ```ts @@ -249,7 +246,5 @@ export const examplePlugin = createBackendPlugin({ }); ``` -Before adding custom configuration options, make sure to read [the configuration -docs](../../conf/index.md), in particular the section on [defining configuration -for your own plugins](../../conf/defining.md) which explains how to establish a +Before adding custom configuration options, make sure to read [the configuration docs](../../conf/index.md), in particular the section on [defining configuration for your own plugins](../../conf/defining.md) which explains how to establish a configuration schema for your specific plugin. diff --git a/docs/backend-system/building-plugins-and-modules/02-testing.md b/docs/backend-system/building-plugins-and-modules/02-testing.md index f8af2bcabe..8a2dd0414d 100644 --- a/docs/backend-system/building-plugins-and-modules/02-testing.md +++ b/docs/backend-system/building-plugins-and-modules/02-testing.md @@ -21,8 +21,7 @@ collective term for backend [plugins](../architecture/04-plugins.md) and The function returns an HTTP server instance which can be used together with e.g. `supertest` to easily test the actual REST service surfaces of plugins who -register routes with [the HTTP router service -API](../core-services/01-index.md). +register routes with [the HTTP router service API](../core-services/01-index.md). ```ts import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 064f7c803c..0002e51160 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -198,8 +198,7 @@ in namespace `NAMESPACE` and it has adequate [permissions](#role-based-access-control), here are some sample procedures to procure a long-lived service account token for use with this provider: -- On versions of Kubernetes [prior to - 1.24](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#no-really-you-must-read-this-before-you-upgrade-1), +- On versions of Kubernetes [prior to 1.24](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#no-really-you-must-read-this-before-you-upgrade-1), you could get an (automatically-generated) token for a service account with: ```sh @@ -209,8 +208,7 @@ procure a long-lived service account token for use with this provider: | base64 --decode ``` -- For Kubernetes 1.24+, as described in [this - guide](https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets), +- For Kubernetes 1.24+, as described in [this guide](https://kubernetes.io/docs/concepts/configuration/secret/#service-account-token-secrets), you can obtain a long-lived token by creating a secret: ```sh @@ -235,8 +233,7 @@ procure a long-lived service account token for use with this provider: If a cluster has `authProvider: serviceAccount` and the `serviceAccountToken` field is omitted, Backstage will ignore the configured URL and certificate data, instead attempting to access the Kubernetes API via an in-cluster client as in -[this -example](https://github.com/kubernetes-client/javascript/blob/master/examples/in-cluster.js). +[this example](https://github.com/kubernetes-client/javascript/blob/master/examples/in-cluster.js). ##### `clusters.\*.oidcTokenProvider` (optional) diff --git a/docs/features/kubernetes/proxy.md b/docs/features/kubernetes/proxy.md index 4ccc2d22b1..2da797d8ef 100644 --- a/docs/features/kubernetes/proxy.md +++ b/docs/features/kubernetes/proxy.md @@ -7,12 +7,10 @@ description: Interacting with the Kubernetes API in Backstage plugins [Contributors](https://backstage.io/docs/overview/glossary#backstage-user-profiles) wanting to create developer portal experiences based on data from Kubernetes (e.g. for -interacting with [Custom -Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) +interacting with [Custom Resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) beyond the default behaviors of the existing Kubernetes plugin) can leverage the Kubernetes backend plugin's proxy endpoint to allow them to make arbitrary -requests to the [REST -API](https://kubernetes.io/docs/reference/using-api/api-concepts/). +requests to the [REST API](https://kubernetes.io/docs/reference/using-api/api-concepts/). Here is a snippet fetching namespaces using the `KubernetesBackendClient` library @@ -31,8 +29,7 @@ await kubernetesApi.proxy(CLUSTER_NAME, '/api/v1/namespaces'); The proxy will interpret the [`Backstage-Kubernetes-Cluster`](https://backstage.io/docs/reference/plugin-kubernetes-backend.header_kubernetes_cluster) header as the name of the cluster to target. This name will be compared to each cluster -returned by all the configured [cluster -locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods) +returned by all the configured [cluster locators](https://backstage.io/docs/features/kubernetes/configuration#clusterlocatormethods) -- the first cluster whose [`name` field](https://backstage.io/docs/features/kubernetes/configuration#clustersname) matches the value in the header will be targeted. @@ -48,12 +45,10 @@ The proxy expects a `KubernetesAuthTranslator` to be provided that is used to de ## Authentication The proxy has no provisions for mTLS, so it cannot be used to connect to -clusters using the [x509 Client -Certs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs) +clusters using the [x509 Client Certs](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#x509-client-certs) authentication strategy.\ The current `/proxy` Implementation expects a -[Bearer -token](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request) +[Bearer token](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#putting-a-bearer-token-in-a-request) to be provided as a `Backstage-Kubernetes-Authorization` header for a target cluster. This token will be used as the `Authorization` header when forwarding a request to a target cluster. ## How to disable the proxy endpoint via PermissionPolicy @@ -104,8 +99,7 @@ even if a valid ID token was attached that a cluster would authorize. ## Other known limitations -The proxy as it was released in [Backstage -1.9](https://github.com/backstage/backstage/blob/master/docs/releases/v1.9.0-changelog.md#patch-changes-15) +The proxy as it was released in [Backstage 1.9](../../releases/v1.9.0-changelog.md#patch-changes-15) has a known bug: - [#15901](https://github.com/backstage/backstage/issues/15901) - it cannot diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index c98d6911b4..a65bfb4a44 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -40,8 +40,7 @@ browse the catalog at `http://localhost:3000`. ## Adding components to the catalog -The source of truth for the components in your software catalog are [metadata -YAML files](descriptor-format.md) stored in source control (GitHub, GitHub +The source of truth for the components in your software catalog are [metadata YAML files](descriptor-format.md) stored in source control (GitHub, GitHub Enterprise, GitLab, ...). Repositories can include one or multiple metadata files. Usually the metadata file is located in the repository root. This is not a formal requirement & metadata files can be placed anywhere in the repository. diff --git a/docs/integrations/github/org--old.md b/docs/integrations/github/org--old.md index 2f1b39a1f8..3d1639a924 100644 --- a/docs/integrations/github/org--old.md +++ b/docs/integrations/github/org--old.md @@ -308,8 +308,7 @@ const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { }); ``` -Once you have imported the emails you can resolve users in your [sign-in -resolver](../../auth/github/provider.md) using the catalog entity search via email +Once you have imported the emails you can resolve users in your [sign-in resolver](../../auth/github/provider.md) using the catalog entity search via email ```typescript title="packages/backend/src/plugins/auth.ts" ctx.signInWithCatalogUser({ diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 8e3d73ded6..186619d1df 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -10,8 +10,7 @@ Backstage is currently under rapid development. This page details the project's public roadmap, the result of ongoing collaboration between the core maintainers and the broader Backstage community. -The Backstage roadmap lays out both [“what's next”](#whats-next) and ["future -work"](#future-work). With "next" we mean features planned for release within +The Backstage roadmap lays out both [“what's next”](#whats-next) and ["future work"](#future-work). With "next" we mean features planned for release within the ongoing quarter from July through September 2022. With "future" we mean features on the radar, but not yet scheduled. @@ -52,8 +51,7 @@ the platform. The benefit for the adopters is clear: we want Backstage to be as secure as possible, and we want to make it reliable through a specific initiative. -This initiative is done together with, and with the support of, the [Cloud -Native Computing Foundation (CNCF)](https://www.cncf.io/). +This initiative is done together with, and with the support of, the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/). ### Backstage Threat Model diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 52a859b5e9..10a7421057 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -69,8 +69,7 @@ installed, may be captured. | `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided. | | `not-found` | The path of the resource that resulted in a not found page | Fired by at least TechDocs. | -If there is an event you'd like to see captured, please [open an -issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md&title=[Analytics%20Event]:%20THE+EVENT+TO+CAPTURE) describing the event you want to see and the questions it +If there is an event you'd like to see captured, please [open an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md&title=[Analytics%20Event]:%20THE+EVENT+TO+CAPTURE) describing the event you want to see and the questions it would help you answer. Or jump to [Capturing Events](#capturing-events) to learn how to contribute the instrumentation yourself! diff --git a/docs/releases/v1.12.0-changelog.md b/docs/releases/v1.12.0-changelog.md index df25020aee..49ff79d19a 100644 --- a/docs/releases/v1.12.0-changelog.md +++ b/docs/releases/v1.12.0-changelog.md @@ -60,8 +60,7 @@ This change makes the dependence explicit, and removes the burden on OAuth2-based providers which require an ID token (e.g. this is done by various - default [auth - handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add + default [auth handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add `openid` to their default scopes. _That_ could carry another indirect benefit: by removing `openid` from the default scopes for a provider, grants for resource-specific access tokens can avoid requesting excess ID token-related diff --git a/docs/releases/v1.12.0-next.2-changelog.md b/docs/releases/v1.12.0-next.2-changelog.md index 7a64ec5f84..ed4c5683ba 100644 --- a/docs/releases/v1.12.0-next.2-changelog.md +++ b/docs/releases/v1.12.0-next.2-changelog.md @@ -25,8 +25,7 @@ This change makes the dependence explicit, and removes the burden on OAuth2-based providers which require an ID token (e.g. this is done by various - default [auth - handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add + default [auth handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add `openid` to their default scopes. _That_ could carry another indirect benefit: by removing `openid` from the default scopes for a provider, grants for resource-specific access tokens can avoid requesting excess ID token-related diff --git a/docs/releases/v1.15.0.md b/docs/releases/v1.15.0.md index 336fe7520b..c7ef6b33f9 100644 --- a/docs/releases/v1.15.0.md +++ b/docs/releases/v1.15.0.md @@ -27,8 +27,7 @@ native dependency, and as such needs to be built during `yarn` installation, on the exact architecture that it then executes on. For those who compile and run Backstage on stripped-down environments, you will want to ensure that you have the build basics present, e.g. `build-essential` or similar corresponding to -your operating system of choice. The `isolated-vm` repo has [some further -information](https://github.com/laverdet/isolated-vm#requirements) about the +your operating system of choice. The `isolated-vm` repo has [some further information](https://github.com/laverdet/isolated-vm#requirements) about the build environment requirements. There is a [CVE-2022-39266](https://www.cve.org/CVERecord?id=CVE-2022-39266) @@ -65,8 +64,7 @@ covers of your catalog instance, finding those pesky unprocessed entities that may be stuck in limbo because of an otherwise tricky-to-debug validation issue or similar. -Check out [the plugin’s -README](https://github.com/backstage/backstage/blob/master/plugins/catalog-unprocessed-entities/README.md) +Check out [the plugin’s README](https://github.com/backstage/backstage/blob/master/plugins/catalog-unprocessed-entities/README.md) for details and installation instructions. Contributed by [@alde](https://github.com/alde) in @@ -176,8 +174,7 @@ Contributed by [@sblausten](https://github.com/sblausten) in We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for -[keeping Backstage -updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). +[keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). ## Links and References @@ -185,11 +182,9 @@ Below you can find a list of links and references to help you learn about and start using this new release. - [Backstage official website](https://backstage.io/), - [documentation](https://backstage.io/docs/), and [getting started - guide](https://backstage.io/docs/getting-started/) + [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) - [GitHub repository](https://github.com/backstage/backstage) -- Backstage's [versioning and support - policy](https://backstage.io/docs/overview/versioning-policy) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) - [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.15.0-changelog.md) diff --git a/docs/releases/v1.3.0-changelog.md b/docs/releases/v1.3.0-changelog.md index 641fd8422a..e3cdd75024 100644 --- a/docs/releases/v1.3.0-changelog.md +++ b/docs/releases/v1.3.0-changelog.md @@ -883,8 +883,7 @@ ### Patch Changes - 8f7b1835df: Updated dependency `msw` to `^0.41.0`. -- 811ff4bcf4: Updated `swagger-ui-react` to 4.11.1 in order to address a [XSS - vulnerability](https://github.com/advisories/GHSA-hqq7-2q2v-82xq) in `@braintree/sanitize-url` +- 811ff4bcf4: Updated `swagger-ui-react` to 4.11.1 in order to address a [XSS vulnerability](https://github.com/advisories/GHSA-hqq7-2q2v-82xq) in `@braintree/sanitize-url` - Updated dependencies - @backstage/plugin-catalog@1.3.0 - @backstage/plugin-catalog-react@1.1.1 diff --git a/docs/releases/v1.7.0-changelog.md b/docs/releases/v1.7.0-changelog.md index 3653484952..47fd8463d6 100644 --- a/docs/releases/v1.7.0-changelog.md +++ b/docs/releases/v1.7.0-changelog.md @@ -926,8 +926,7 @@ Migrating to the stable version of `react-router` is optional for the time being. But if you want to do the same for your existing repository, please - follow [this - guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + follow [this guide](https://backstage.io/docs/tutorials/react-router-stable-migration). - e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo diff --git a/docs/releases/v1.7.0-next.0-changelog.md b/docs/releases/v1.7.0-next.0-changelog.md index 57b3c64f6f..41d3ebd55f 100644 --- a/docs/releases/v1.7.0-next.0-changelog.md +++ b/docs/releases/v1.7.0-next.0-changelog.md @@ -319,8 +319,7 @@ Migrating to the stable version of `react-router` is optional for the time being. But if you want to do the same for your existing repository, please - follow [this - guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + follow [this guide](https://backstage.io/docs/tutorials/react-router-stable-migration). - e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo diff --git a/docs/releases/v1.7.0.md b/docs/releases/v1.7.0.md index 56eda8b20d..edd5cbb591 100644 --- a/docs/releases/v1.7.0.md +++ b/docs/releases/v1.7.0.md @@ -22,24 +22,20 @@ backend needs to be supplied with a location analyzer for this use case to continue to function. If you want to make use of this feature, check out the installation instructions -in [the -changelog](https://github.com/backstage/backstage/blob/master/plugins/catalog-import/CHANGELOG.md#090). +in [the changelog](https://github.com/backstage/backstage/blob/master/plugins/catalog-import/CHANGELOG.md#090). Contributed by [@kissmikijr](https://github.com/kissmikijr) in [#13800](https://github.com/backstage/backstage/pull/13800) ### Permission Rule Changes -When defining permission rules, it's now necessary to provide a [Zod -Schema](https://github.com/colinhacks/zod) that specifies the parameters the +When defining permission rules, it's now necessary to provide a [Zod Schema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. The signatures of the rule methods (`apply` and `toQuery`) have changed slightly as well. -You can read more about this in [the permissions -documentation](https://backstage.io/docs/permissions/overview) and [the -changelog](https://github.com/backstage/backstage/blob/master/plugins/permission-node/CHANGELOG.md#070). +You can read more about this in [the permissions documentation](https://backstage.io/docs/permissions/overview) and [the changelog](https://github.com/backstage/backstage/blob/master/plugins/permission-node/CHANGELOG.md#070). ### Migration: `jest` v29 @@ -64,8 +60,7 @@ Newly created Backstage repositories now use the stable version 6 of Migrating to the stable version of `react-router` is optional for the time being; Backstage has support for both versions. But if you want to do the same -for your existing repository, please follow [this -guide](https://backstage.io/docs/tutorials/react-router-stable-migration). +for your existing repository, please follow [this guide](https://backstage.io/docs/tutorials/react-router-stable-migration). Support for the beta version will be removed in a later release. ### Support for `__mocks__` and `__testUtils__` directories @@ -100,8 +95,7 @@ This release does not contain any security fixes. We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for -[keeping Backstage -updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). +[keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). ## Links and References @@ -109,11 +103,9 @@ Below you can find a list of links and references to help you learn about and start using this new release. - [Backstage official website](https://backstage.io/), - [documentation](https://backstage.io/docs/), and [getting started - guide](https://backstage.io/docs/getting-started/) + [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) - [GitHub repository](https://github.com/backstage/backstage) -- Backstage's [versioning and support - policy](https://backstage.io/docs/overview/versioning-policy) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) - [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support - [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.7.0-changelog.md) - Backstage [Demos](https://backstage.io/demos), diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index c13bf1613e..f21eed47d0 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -539,8 +539,7 @@ This change makes the dependence explicit, and removes the burden on OAuth2-based providers which require an ID token (e.g. this is done by various - default [auth - handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add + default [auth handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add `openid` to their default scopes. _That_ could carry another indirect benefit: by removing `openid` from the default scopes for a provider, grants for resource-specific access tokens can avoid requesting excess ID token-related @@ -568,8 +567,7 @@ This change makes the dependence explicit, and removes the burden on OAuth2-based providers which require an ID token (e.g. this is done by various - default [auth - handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add + default [auth handlers](https://backstage.io/docs/auth/identity-resolver/#authhandler)) to add `openid` to their default scopes. _That_ could carry another indirect benefit: by removing `openid` from the default scopes for a provider, grants for resource-specific access tokens can avoid requesting excess ID token-related diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index b301828207..ee49b7d8f5 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1134,8 +1134,7 @@ Migrating to the stable version of `react-router` is optional for the time being. But if you want to do the same for your existing repository, please - follow [this - guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + follow [this guide](https://backstage.io/docs/tutorials/react-router-stable-migration). - e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo - 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. @@ -1182,8 +1181,7 @@ Migrating to the stable version of `react-router` is optional for the time being. But if you want to do the same for your existing repository, please - follow [this - guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + follow [this guide](https://backstage.io/docs/tutorials/react-router-stable-migration). - e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo - 52f25858a8: Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. @@ -3166,8 +3164,7 @@ The old `sqlite3` NPM library has been abandoned by its maintainers, which has led to unhandled security reports and other issues. Therefore, in the `knex` 1.x - release line they have instead switched over to the [`@vscode/sqlite3` - library](https://github.com/microsoft/vscode-node-sqlite3) by default, which is + release line they have instead switched over to the [`@vscode/sqlite3` library](https://github.com/microsoft/vscode-node-sqlite3) by default, which is actively maintained by Microsoft. This means that as you update to this version of Backstage, there are two @@ -3236,8 +3233,7 @@ The old `sqlite3` NPM library has been abandoned by its maintainers, which has led to unhandled security reports and other issues. Therefore, in the `knex` 1.x - release line they have instead switched over to the [`@vscode/sqlite3` - library](https://github.com/microsoft/vscode-node-sqlite3) by default, which is + release line they have instead switched over to the [`@vscode/sqlite3` library](https://github.com/microsoft/vscode-node-sqlite3) by default, which is actively maintained by Microsoft. This means that as you update to this version of Backstage, there are two diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 39613bd409..24b30e2fda 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1349,8 +1349,7 @@ ### Patch Changes - 8f7b1835df: Updated dependency `msw` to `^0.41.0`. -- 811ff4bcf4: Updated `swagger-ui-react` to 4.11.1 in order to address a [XSS - vulnerability](https://github.com/advisories/GHSA-hqq7-2q2v-82xq) in `@braintree/sanitize-url` +- 811ff4bcf4: Updated `swagger-ui-react` to 4.11.1 in order to address a [XSS vulnerability](https://github.com/advisories/GHSA-hqq7-2q2v-82xq) in `@braintree/sanitize-url` - Updated dependencies - @backstage/plugin-catalog@1.3.0 - @backstage/plugin-catalog-react@1.1.1 diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 4d8e2f1cd0..cb4e25d190 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -3574,8 +3574,7 @@ `packages/backend/src/plugins/catalog.ts` creates the catalog builder using `CatalogBuilder.create`. If you instead call `new CatalogBuilder`, you are on the old implementation and will experience breakage if you upgrade to this - version. If you are still on the old version, see [the relevant change log - entry](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/CHANGELOG.md#patch-changes-27) + version. If you are still on the old version, see [the relevant change log entry](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/CHANGELOG.md#patch-changes-27) for migration instructions. The minimal `packages/backend/src/plugins/catalog.ts` file is now: @@ -3668,8 +3667,7 @@ `packages/backend/src/plugins/catalog.ts` creates the catalog builder using `CatalogBuilder.create`. If you instead call `new CatalogBuilder`, you are on the old implementation and will experience breakage if you upgrade to this - version. If you are still on the old version, see [the relevant change log - entry](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/CHANGELOG.md#patch-changes-27) + version. If you are still on the old version, see [the relevant change log entry](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/CHANGELOG.md#patch-changes-27) for migration instructions. The minimal `packages/backend/src/plugins/catalog.ts` file is now: diff --git a/plugins/catalog-backend/README.md b/plugins/catalog-backend/README.md index 7f886de155..c2fbe713b9 100644 --- a/plugins/catalog-backend/README.md +++ b/plugins/catalog-backend/README.md @@ -1,9 +1,7 @@ # Catalog Backend -This is the backend for the default Backstage [software -catalog](http://backstage.io/docs/features/software-catalog/). -This provides an API for consumers such as the frontend [catalog -plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog). +This is the backend for the default Backstage [software catalog](http://backstage.io/docs/features/software-catalog/). +This provides an API for consumers such as the frontend [catalog plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog). It comes with a builtin database-backed implementation of the catalog that can store and serve your catalog for you. @@ -34,8 +32,7 @@ yarn --cwd packages/backend add @backstage/plugin-catalog-backend You'll need to add the plugin to the router in your `backend` package. You can do this by creating a file called `packages/backend/src/plugins/catalog.ts` with -contents matching [catalog.ts in the create-app -template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts). +contents matching [catalog.ts in the create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts). With the `catalog.ts` router setup in place, add the router to `packages/backend/src/index.ts`: @@ -60,10 +57,8 @@ async function main() { ### Adding catalog entities At this point the `catalog-backend` is installed in your backend package, but -you will not have any catalog entities loaded. See [Catalog -Configuration](https://backstage.io/docs/features/software-catalog/configuration) -for how to add locations, or copy the catalog locations from the [create-app -template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs) +you will not have any catalog entities loaded. See [Catalog Configuration](https://backstage.io/docs/features/software-catalog/configuration) +for how to add locations, or copy the catalog locations from the [create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs) to get up and running quickly. ## Development diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index fe64a6101d..bb2477302e 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -2945,8 +2945,7 @@ `CatalogPage` component in your `App.tsx` routing allows you to adjust the layout, header, and which filters are available. - See the documentation added on [Catalog - Customization](https://backstage.io/docs/features/software-catalog/catalog-customization) + See the documentation added on [Catalog Customization](https://backstage.io/docs/features/software-catalog/catalog-customization) for instructions. ### Patch Changes diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index d20331bbfe..abfcf1012c 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -3361,8 +3361,7 @@ `CatalogPage` component in your `App.tsx` routing allows you to adjust the layout, header, and which filters are available. - See the documentation added on [Catalog - Customization](https://backstage.io/docs/features/software-catalog/catalog-customization) + See the documentation added on [Catalog Customization](https://backstage.io/docs/features/software-catalog/catalog-customization) for instructions. ### Patch Changes diff --git a/plugins/catalog/README.md b/plugins/catalog/README.md index 1ce7b2bf69..9375c14a4a 100644 --- a/plugins/catalog/README.md +++ b/plugins/catalog/README.md @@ -1,7 +1,6 @@ # Backstage Catalog Frontend -This is the React frontend for the default Backstage [software -catalog](http://backstage.io/docs/features/software-catalog/). +This is the React frontend for the default Backstage [software catalog](http://backstage.io/docs/features/software-catalog/). This package supplies interfaces related to listing catalog entities or showing more information about them on entity pages. diff --git a/plugins/scaffolder-backend/README.md b/plugins/scaffolder-backend/README.md index 0aa57fd90c..c0629f24f0 100644 --- a/plugins/scaffolder-backend/README.md +++ b/plugins/scaffolder-backend/README.md @@ -1,9 +1,7 @@ # Scaffolder Backend -This is the backend for the default Backstage [software -templates](https://backstage.io/docs/features/software-templates/). -This provides the API for the frontend [scaffolder -plugin](https://github.com/backstage/backstage/tree/master/plugins/scaffolder), +This is the backend for the default Backstage [software templates](https://backstage.io/docs/features/software-templates/). +This provides the API for the frontend [scaffolder plugin](https://github.com/backstage/backstage/tree/master/plugins/scaffolder), as well as the built-in template actions, tasks and stages. ## Installation @@ -28,8 +26,7 @@ yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend You'll need to add the plugin to the router in your `backend` package. You can do this by creating a file called `packages/backend/src/plugins/scaffolder.ts` -with contents matching [scaffolder.ts in the create-app -template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts). +with contents matching [scaffolder.ts in the create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts). With the `scaffolder.ts` router setup in place, add the router to `packages/backend/src/index.ts`: @@ -54,10 +51,7 @@ async function main() { ### Adding templates At this point the scaffolder backend is installed in your backend package, but -you will not have any templates available to use. These need to be [added to the -software -catalog](https://backstage.io/docs/features/software-templates/adding-templates). +you will not have any templates available to use. These need to be [added to the software catalog](https://backstage.io/docs/features/software-templates/adding-templates). To get up and running and try out some templates quickly, you can or copy the -catalog locations from the [create-app -template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs). +catalog locations from the [create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs). diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 4a9e1b870b..9011c681cb 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -641,8 +641,7 @@ - e72d371296: Added `TemplateEntityV1beta2` which was moved here from `@backstage/plugin-scaffolder-common`. It has also been marked as deprecated in - the process - please consider [migrating to `v1beta3` - templates](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3). + the process - please consider [migrating to `v1beta3` templates](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3). - c77c5c7eb6: Added `backstage.role` to `package.json` - Updated dependencies - @backstage/catalog-model@0.10.0 diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index a6af441d62..8acf456b08 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -1,7 +1,6 @@ # Scaffolder Frontend -This is the React frontend for the default Backstage [software -templates](https://backstage.io/docs/features/software-templates/). +This is the React frontend for the default Backstage [software templates](https://backstage.io/docs/features/software-templates/). This package supplies interfaces related to showing available templates in the Backstage catalog and the workflow to create software using those templates. diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 1ecaaeb536..41a6930ae3 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -8,8 +8,7 @@ This plugin provides two components, `` is intended to be used w It also provides a `UserSettingsStorage` implementation of the `StorageApi`, to be used in the frontend as a persistent alternative to the builtin `WebStorage`. -Please see [the backend -README](https://github.com/backstage/backstage/tree/master/plugins/user-settings-backend) +Please see [the backend README](https://github.com/backstage/backstage/tree/master/plugins/user-settings-backend) for installation instructions. ## Components Usage From 6a3127526de149024c566f699f9072ffdd9c8b8e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 10 May 2024 10:21:40 +0200 Subject: [PATCH 340/567] catalog,scaffolder: fix backend installation instructions Signed-off-by: Patrik Oldsberg --- plugins/catalog-backend/README.md | 16 ++++++++++++---- plugins/scaffolder-backend/README.md | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend/README.md b/plugins/catalog-backend/README.md index c2fbe713b9..b0bd0328ab 100644 --- a/plugins/catalog-backend/README.md +++ b/plugins/catalog-backend/README.md @@ -28,11 +28,19 @@ restoring the plugin, if you previously removed it. yarn --cwd packages/backend add @backstage/plugin-catalog-backend ``` -### Adding the plugin to your `packages/backend` +Then add the plugin to your backend, typically in `packages/backend/src/index.ts`: -You'll need to add the plugin to the router in your `backend` package. You can -do this by creating a file called `packages/backend/src/plugins/catalog.ts` with -contents matching [catalog.ts in the create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts). +```ts +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +``` + +#### Old backend system + +In the old backend system there's a bit more wiring required. You'll need to +create a file called `packages/backend/src/plugins/catalog.ts` with contents +matching [catalog.ts in the create-app template](https://github.com/backstage/backstage/blob/ad9314d3a7e0405719ba93badf96e97adde8ef83/packages/create-app/templates/default-app/packages/backend/src/plugins/catalog.ts). With the `catalog.ts` router setup in place, add the router to `packages/backend/src/index.ts`: diff --git a/plugins/scaffolder-backend/README.md b/plugins/scaffolder-backend/README.md index c0629f24f0..d760b0fc2d 100644 --- a/plugins/scaffolder-backend/README.md +++ b/plugins/scaffolder-backend/README.md @@ -22,11 +22,19 @@ restoring the plugin, if you previously removed it. yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend ``` -### Adding the plugin to your `packages/backend` +Then add the plugin to your backend, typically in `packages/backend/src/index.ts`: -You'll need to add the plugin to the router in your `backend` package. You can -do this by creating a file called `packages/backend/src/plugins/scaffolder.ts` -with contents matching [scaffolder.ts in the create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts). +```ts +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); +``` + +#### Old backend system + +In the old backend system there's a bit more wiring required. You'll need to +create a file called `packages/backend/src/plugins/scaffolder.ts` +with contents matching [scaffolder.ts in the create-app template](https://github.com/backstage/backstage/blob/ad9314d3a7e0405719ba93badf96e97adde8ef83/packages/create-app/templates/default-app/packages/backend/src/plugins/scaffolder.ts). With the `scaffolder.ts` router setup in place, add the router to `packages/backend/src/index.ts`: From 4e8083f83194f1b44959989c06fca3aef8af89b0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 10 May 2024 11:11:14 +0200 Subject: [PATCH 341/567] OWNERS.md: add org member secustor Signed-off-by: Patrik Oldsberg --- OWNERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS.md b/OWNERS.md index d8967ca873..f2e2cb7cf1 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -187,6 +187,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | Miklós Kiss | Roadie.io | [kissmikijr](https://github.com/kissmikijr) | `Miklos#7416` | | Patrick Jungermann | Bonial International GmbH | [pjungermann](https://github.com/pjungermann) | `pjungermann#6933` | | Phil Kuang | FactSet Research Systems | [kuangp](https://github.com/kuangp) | `pkuang#3202` | +| Sebastian Poxhofer | N26 | [secustor](https://github.com/secustor) | `secustor` | | Taras Mankovski | Frontside | [taras](https://github.com/taras) | `tarasm#1256` | ## Emeritus Core Maintainers From 131e5cbfe86811734c62c185d73226ad5f14736f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 10 May 2024 10:22:27 +0200 Subject: [PATCH 342/567] .changesets: added changeset for fixing multi-line links in README Signed-off-by: Patrik Oldsberg --- .changeset/brave-planets-raise.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/brave-planets-raise.md diff --git a/.changeset/brave-planets-raise.md b/.changeset/brave-planets-raise.md new file mode 100644 index 0000000000..9aec85ae6e --- /dev/null +++ b/.changeset/brave-planets-raise.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-catalog': patch +--- + +Fix broken links in README. From c3623c0cb62906908aed41c014a21e5e6a49d6c7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 10 May 2024 10:27:58 +0200 Subject: [PATCH 343/567] docs: add check and fix links spanning 3 lines Signed-off-by: Patrik Oldsberg --- docs/releases/v1.6.0-changelog.md | 4 +--- docs/releases/v1.6.0-next.1-changelog.md | 4 +--- packages/cli/CHANGELOG.md | 8 ++------ scripts/verify-links.js | 3 ++- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/docs/releases/v1.6.0-changelog.md b/docs/releases/v1.6.0-changelog.md index 0308c5fd3d..0532243cbf 100644 --- a/docs/releases/v1.6.0-changelog.md +++ b/docs/releases/v1.6.0-changelog.md @@ -24,9 +24,7 @@ - 1fe6823bb5: Updated dependency `eslint-plugin-jest` to `^27.0.0`. Note that this major update to the Jest plugin contains some breaking changes. - This means that some of your tests may start seeing some new lint errors. [Read - about them - here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). + This means that some of your tests may start seeing some new lint errors. [Read about them here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). These are mostly possible to fix automatically. You can try to run `yarn backstage-cli repo lint --fix` in your repo root to have most or all of them corrected. diff --git a/docs/releases/v1.6.0-next.1-changelog.md b/docs/releases/v1.6.0-next.1-changelog.md index 3a1dc8f1b7..e749e32c98 100644 --- a/docs/releases/v1.6.0-next.1-changelog.md +++ b/docs/releases/v1.6.0-next.1-changelog.md @@ -7,9 +7,7 @@ - 1fe6823bb5: Updated dependency `eslint-plugin-jest` to `^27.0.0`. Note that this major update to the Jest plugin contains some breaking changes. - This means that some of your tests may start seeing some new lint errors. [Read - about them - here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). + This means that some of your tests may start seeing some new lint errors. [Read about them here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). These are mostly possible to fix automatically. You can try to run `yarn backstage-cli repo lint --fix` in your repo root to have most or all of them corrected. diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 804966ba3b..2b784d7093 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1628,9 +1628,7 @@ - 1fe6823bb5: Updated dependency `eslint-plugin-jest` to `^27.0.0`. Note that this major update to the Jest plugin contains some breaking changes. - This means that some of your tests may start seeing some new lint errors. [Read - about them - here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). + This means that some of your tests may start seeing some new lint errors. [Read about them here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). These are mostly possible to fix automatically. You can try to run `yarn backstage-cli repo lint --fix` in your repo root to have most or all of them corrected. @@ -1764,9 +1762,7 @@ - 1fe6823bb5: Updated dependency `eslint-plugin-jest` to `^27.0.0`. Note that this major update to the Jest plugin contains some breaking changes. - This means that some of your tests may start seeing some new lint errors. [Read - about them - here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). + This means that some of your tests may start seeing some new lint errors. [Read about them here](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md#2700-2022-08-28). These are mostly possible to fix automatically. You can try to run `yarn backstage-cli repo lint --fix` in your repo root to have most or all of them corrected. diff --git a/scripts/verify-links.js b/scripts/verify-links.js index 26d31ce6f0..cb7385d972 100755 --- a/scripts/verify-links.js +++ b/scripts/verify-links.js @@ -137,7 +137,8 @@ async function verifyFile(filePath, docPages) { } } - const multiLineLinks = content.match(/\[[^\]\n]+?\n[^\]\n]*?\]\(/g) || []; + const multiLineLinks = + content.match(/\[[^\]\n]+?\n[^\]\n]*?(?:\n[^\]\n]*?)?\]\(/g) || []; badUrls.push( ...multiLineLinks.map(url => ({ url, From e4f9d276d69aa01836061bfce5980f46a6b19185 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Fri, 10 May 2024 15:56:44 +0530 Subject: [PATCH 344/567] Updated the auth document Signed-off-by: Aditya Kumar --- docs/auth/index.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/auth/index.md b/docs/auth/index.md index f6282264e4..f925e52917 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -10,12 +10,16 @@ configure Backstage to have any number of authentication providers, but only one of these will typically be used for sign-in, with the rest being used to provide access to external resources. -> NOTE: Identity management and the Sign-In page in Backstage is NOT a method for blocking -> access for unauthorized users. The identity system only serves to provide a personalized -> experience and access to a Backstage Identity Token, which can be passed to backend plugins. -> This also means that your Backstage backend APIs are by default unauthenticated. -> Thus, if your Backstage instance is exposed to the Internet, anyone can access -> information in the Backstage. You can learn more [here](../overview/threat-model.md#integrator-responsibilities). +:::note Note + +Identity management and the Sign-In page in Backstage is NOT a method for blocking +access for unauthorized users. The identity system only serves to provide a personalized +experience and access to a Backstage Identity Token, which can be passed to backend plugins. +This also means that your Backstage backend APIs are by default unauthenticated. +Thus, if your Backstage instance is exposed to the Internet, anyone can access +information in the Backstage. You can learn more [here](../overview/threat-model.md#integrator-responsibilities). + +::: ## Built-in Authentication Providers @@ -141,8 +145,12 @@ const app = createApp({ }); ``` -> NOTE: You can configure sign-in to use a redirect flow with no pop-up by adding -> `enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml` +:::note Note + +You can configure sign-in to use a redirect flow with no pop-up by adding +`enableExperimentalRedirectFlow: true` to the root of your `app-config.yaml` + +::: ## Sign-In with Proxy Providers From 9248b06316302fe610bde4ea6dac0d79776afef7 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 10 May 2024 13:33:12 +0200 Subject: [PATCH 345/567] github-org: add org cleaner provider Signed-off-by: Vincenzo Scamporlino --- .../src/GithubOrgEntityCleanerProvider.tsx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx diff --git a/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx b/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx new file mode 100644 index 0000000000..386fd367c2 --- /dev/null +++ b/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx @@ -0,0 +1,38 @@ +/* + * 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 { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-node'; + +export class GithubOrgEntityCleanerProvider implements EntityProvider { + constructor(private readonly options: { id: string }) {} + + getProviderName() { + return `GithubOrgEntityProvider:${this.options.id}`; + } + async connect(connection: EntityProviderConnection) { + // Clean up any existing entities + connection + .applyMutation({ + type: 'full', + entities: [], + }) + .catch(error => { + console.error('Failed to clean up entities', error); + }); + } +} From 14af3cb60f3e922633e255390cb7d2a18ebca22a Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 10 May 2024 13:34:00 +0200 Subject: [PATCH 346/567] github-org: use default namespace if a single provider is instantiated Signed-off-by: Vincenzo Scamporlino --- .../catalog-backend-module-github-org/src/module.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index 91e0cff049..517968c4f0 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -31,6 +31,7 @@ import { } from '@backstage/plugin-catalog-backend-module-github'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { GithubOrgEntityCleanerProvider } from './GithubOrgEntityCleanerProvider'; /** * Interface for {@link githubOrgEntityProviderTransformsExtensionPoint}. @@ -99,8 +100,14 @@ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ logger: coreServices.logger, scheduler: coreServices.scheduler, }, + async init({ catalog, config, events, logger, scheduler }) { - for (const definition of readDefinitionsFromConfig(config)) { + const definitions = readDefinitionsFromConfig(config); + + for (const definition of definitions) { + catalog.addEntityProvider( + new GithubOrgEntityCleanerProvider({ id: definition.id }), + ); catalog.addEntityProvider( GithubMultiOrgEntityProvider.fromConfig(config, { id: definition.id, @@ -113,7 +120,8 @@ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ logger, userTransformer, teamTransformer, - defaultNamespace: definition.orgs?.length === 1, + defaultNamespace: + definitions.length === 1 && definition.orgs?.length === 1, }), ); } From 4cd526e1ea449aa247645819ef784fd8bdcbdb74 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 10 May 2024 13:39:36 +0200 Subject: [PATCH 347/567] docs: improve migration docs Signed-off-by: Vincenzo Scamporlino --- .../building-backends/08-migrating.md | 75 +++++++++++++++++-- docs/integrations/github/org.md | 2 +- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index 0fbb368449..fc0bf35267 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -452,7 +452,7 @@ catalog: /* highlight-add-end */ ``` -To migrate `GithubMultiOrgEntityProvider` and `GithubOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github-org`. +To migrate `GithubMultiOrgEntityProvider` or `GithubOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github-org`. ```ts title="packages/backend/src/index.ts" backend.add(import('@backstage/plugin-catalog-backend/alpha')); @@ -461,20 +461,79 @@ backend.add(import('@backstage/plugin-catalog-backend-module-github-org')); /* highlight-add-end */ ``` -If you were providing a `schedule` in code, this now needs to be set via configuration. -All other Github configuration in `app-config.yaml` remains the same. +##### GithubOrgEntityProvider + +If you were using `GithubOrgEntityProvider` you might have been configured in code like this: + +```ts title="packages/backend/src/plugins/catalog.ts" +// The org URL below needs to match a configured integrations.github entry +// specified in your app-config. +builder.addEntityProvider( + GithubOrgEntityProvider.fromConfig(env.config, { + id: 'production', + orgUrl: 'https://github.com/backstage', + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }), +); +``` + +This now needs to be set via configuration. The options defined above are now set in `app-config.yaml` instead as shown below: ```yaml title="app-config.yaml" catalog: + /* highlight-add-start */ providers: githubOrg: - yourProviderId: - # ... - /* highlight-add-start */ + - id: production + githubUrl: 'https://github.com', + orgs: ['backstage'] schedule: frequency: PT30M - timeout: PT3M - /* highlight-add-end */ + timeout: PT15M + /* highlight-add-end */ +``` + +##### GithubMultiOrgEntityProvider + +If you were using `GithubMultiOrgEntityProvider` you might have been configured in code like this: + +```ts title="packages/backend/src/plugins/catalog.ts" +// The GitHub URL below needs to match a configured integrations.github entry +// specified in your app-config. +builder.addEntityProvider( + GithubMultiOrgEntityProvider.fromConfig(env.config, { + id: 'production', + githubUrl: 'https://github.com', + // Set the following to list the GitHub orgs you wish to ingest from. You can + // also omit this option to ingest all orgs accessible by your GitHub integration + orgs: ['org-a', 'org-b'], + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }), +); +``` + +This now needs to be set via configuration. The options defined above are now set in `app-config.yaml` instead as shown below: + +```yaml title="app-config.yaml" +catalog: + /* highlight-add-start */ + providers: + githubOrg: + - id: production + githubUrl: 'https://github.com', + orgs: ['org-a', 'org-b'], + schedule: + frequency: PT30M + timeout: PT15M + /* highlight-add-end */ ``` If you were providing transformers, these can be configured by extending `githubOrgEntityProviderTransformsExtensionPoint` diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 0b33cfa184..94eb551c7c 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -45,7 +45,7 @@ Next add the basic configuration to `app-config.yaml` catalog: providers: githubOrg: - id: github + id: production githubUrl: https://github.com orgs: ['organization-1', 'organization-2', 'organization-3'] schedule: From 173987b38d65a9d5623eddfb3c6dc40e33e85ab9 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Fri, 10 May 2024 17:23:07 +0530 Subject: [PATCH 348/567] changeset change Signed-off-by: npiyush97 --- .changeset/sweet-spiders-rhyme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/sweet-spiders-rhyme.md b/.changeset/sweet-spiders-rhyme.md index 58767cdfde..71dcc375c0 100644 --- a/.changeset/sweet-spiders-rhyme.md +++ b/.changeset/sweet-spiders-rhyme.md @@ -1,5 +1,4 @@ --- -'@backstage/plugin-catalog-react': patch '@backstage/core-components': patch --- From f66bbb408795bf00ff1847c36db13db9db82d366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 10 May 2024 12:32:16 +0200 Subject: [PATCH 349/567] do not create a unique connection pool for every CacheService instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/nervous-mayflies-float.md | 5 ++ .github/workflows/ci.yml | 10 +++ .../cache/CacheManager.integration.test.ts | 88 +++++++++++++++++++ .../backend-common/src/cache/CacheManager.ts | 78 ++++++++-------- 4 files changed, 142 insertions(+), 39 deletions(-) create mode 100644 .changeset/nervous-mayflies-float.md create mode 100644 packages/backend-common/src/cache/CacheManager.integration.test.ts diff --git a/.changeset/nervous-mayflies-float.md b/.changeset/nervous-mayflies-float.md new file mode 100644 index 0000000000..e8d29be280 --- /dev/null +++ b/.changeset/nervous-mayflies-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Only create a single actual connection to memcache/redis, even in cases where many `CacheService` instances are made diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c538ec8957..ae90ba164b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -187,6 +187,15 @@ jobs: --health-retries 5 ports: - 3306/tcp + redis: + image: redis + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379/tcp env: CI: true @@ -231,6 +240,7 @@ jobs: BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored + BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }} # We run the test cases before verifying the specs to prevent any failing tests from causing errors. - name: verify openapi specs against test cases diff --git a/packages/backend-common/src/cache/CacheManager.integration.test.ts b/packages/backend-common/src/cache/CacheManager.integration.test.ts new file mode 100644 index 0000000000..7dc60a9051 --- /dev/null +++ b/packages/backend-common/src/cache/CacheManager.integration.test.ts @@ -0,0 +1,88 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { CacheManager } from './CacheManager'; +import KeyvRedis from '@keyv/redis'; + +// This test is in a separate file because the main test file uses other mocking +// that might interfere with this one. + +// Contrived code because it's hard to spy on a default export +jest.mock('@keyv/redis', () => { + const ActualKeyvRedis = jest.requireActual('@keyv/redis'); + return jest + .fn() + .mockImplementation((...args: any[]) => new ActualKeyvRedis(...args)); +}); + +describe('CacheManager integration', () => { + describe('redis', () => { + it('only creates one underlying connection', async () => { + const manager = CacheManager.fromConfig( + new ConfigReader({ + backend: { + cache: { + store: 'redis', + // no actual connection errors will be seen since we don't interact with it + connection: 'redis://localhost:6379', + }, + }, + }), + ); + + manager.forPlugin('p1').getClient(); + manager.forPlugin('p1').getClient({ defaultTtl: 200 }); + manager.forPlugin('p2').getClient(); + manager.forPlugin('p3').getClient({}); + + expect(KeyvRedis).toHaveBeenCalledTimes(1); + }); + + it('interacts correctly with redis', async () => { + // TODO(freben): This could be frameworkified as TestCaches just like + // TestDatabases, but that will have to come some other day + const connection = + process.env.BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING; + if (!connection) { + return; + } + + const manager = CacheManager.fromConfig( + new ConfigReader({ + backend: { + cache: { + store: 'redis', + connection, + }, + }, + }), + ); + + const plugin1 = manager.forPlugin('p1').getClient(); + const plugin2a = manager.forPlugin('p2').getClient(); + const plugin2b = manager.forPlugin('p2').getClient({ defaultTtl: 2000 }); + + await plugin1.set('a', 'plugin1'); + await plugin2a.set('a', 'plugin2a'); + await plugin2b.set('a', 'plugin2b'); + + await expect(plugin1.get('a')).resolves.toBe('plugin1'); + await expect(plugin2a.get('a')).resolves.toBe('plugin2b'); + await expect(plugin2b.get('a')).resolves.toBe('plugin2b'); + }); + }); +}); diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index c7298ab3e9..ec8a1d93f7 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -27,6 +27,8 @@ import { getRootLogger } from '../logging'; import { DefaultCacheClient } from './CacheClient'; import { CacheManagerOptions, PluginCacheManager } from './types'; +type StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv; + /** * Implements a Cache Manager which will automatically create new cache clients * for plugins when requested. All requested cache clients are created with the @@ -40,18 +42,11 @@ export class CacheManager { * that return Keyv instances appropriate to the store. */ private readonly storeFactories = { - redis: this.getRedisClient, - memcache: this.getMemcacheClient, - memory: this.getMemoryClient, + redis: this.createRedisStoreFactory(), + memcache: this.createMemcacheStoreFactory(), + memory: this.createMemoryStoreFactory(), }; - /** - * Shared memory store for the in-memory cache client. Sharing the same Map - * instance ensures get/set/delete operations hit the same store, regardless - * of where/when a client is instantiated. - */ - private readonly memoryStore = new Map(); - private readonly logger: LoggerService; private readonly store: keyof CacheManager['storeFactories']; private readonly connection: string; @@ -148,41 +143,46 @@ export class CacheManager { } private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv { - return this.storeFactories[this.store].call(this, pluginId, ttl); + return this.storeFactories[this.store](pluginId, ttl); } - private getRedisClient( - pluginId: string, - defaultTtl: number | undefined, - ): Keyv { - return new Keyv({ - namespace: pluginId, - ttl: defaultTtl, - store: new KeyvRedis(this.connection), - useRedisSets: this.useRedisSets, - }); + private createRedisStoreFactory(): StoreFactory { + let store: KeyvRedis | undefined; + return (pluginId, defaultTtl) => { + if (!store) { + store = new KeyvRedis(this.connection); + } + return new Keyv({ + namespace: pluginId, + ttl: defaultTtl, + store, + useRedisSets: this.useRedisSets, + }); + }; } - private getMemcacheClient( - pluginId: string, - defaultTtl: number | undefined, - ): Keyv { - return new Keyv({ - namespace: pluginId, - ttl: defaultTtl, - store: new KeyvMemcache(this.connection), - }); + private createMemcacheStoreFactory(): StoreFactory { + let store: KeyvMemcache | undefined; + return (pluginId, defaultTtl) => { + if (!store) { + store = new KeyvMemcache(this.connection); + } + return new Keyv({ + namespace: pluginId, + ttl: defaultTtl, + store, + }); + }; } - private getMemoryClient( - pluginId: string, - defaultTtl: number | undefined, - ): Keyv { - return new Keyv({ - namespace: pluginId, - ttl: defaultTtl, - store: this.memoryStore, - }); + private createMemoryStoreFactory(): StoreFactory { + const store = new Map(); + return (pluginId, defaultTtl) => + new Keyv({ + namespace: pluginId, + ttl: defaultTtl, + store, + }); } } From 2deb523f3d2bdcf098d20f57060dd0de7e2f11d0 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 10 May 2024 14:46:48 +0200 Subject: [PATCH 350/567] backend-app-api: accept camelCase csp directives Signed-off-by: Vincenzo Scamporlino --- packages/backend-app-api/src/http/readHelmetOptions.test.ts | 2 +- packages/backend-app-api/src/http/readHelmetOptions.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/backend-app-api/src/http/readHelmetOptions.test.ts b/packages/backend-app-api/src/http/readHelmetOptions.test.ts index bc31404634..619a4e6a17 100644 --- a/packages/backend-app-api/src/http/readHelmetOptions.test.ts +++ b/packages/backend-app-api/src/http/readHelmetOptions.test.ts @@ -47,7 +47,7 @@ describe('readHelmetOptions', () => { csp: { key: ['value'], 'img-src': false, - 'script-src-attr': ['custom'], + scriptSrcAttr: ['custom'], }, }); expect(readHelmetOptions(config)).toEqual({ diff --git a/packages/backend-app-api/src/http/readHelmetOptions.ts b/packages/backend-app-api/src/http/readHelmetOptions.ts index 0555bdef00..510fdd9586 100644 --- a/packages/backend-app-api/src/http/readHelmetOptions.ts +++ b/packages/backend-app-api/src/http/readHelmetOptions.ts @@ -18,6 +18,7 @@ import { Config } from '@backstage/config'; import helmet from 'helmet'; import { HelmetOptions } from 'helmet'; import { ContentSecurityPolicyOptions } from 'helmet/dist/types/middlewares/content-security-policy'; +import kebabCase from 'lodash/kebabCase'; /** * Attempts to read Helmet options from the backend configuration object. @@ -97,10 +98,11 @@ export function applyCspDirectives( if (directives) { for (const [key, value] of Object.entries(directives)) { + const kebabCaseKey = kebabCase(key); if (value === false) { - delete result[key]; + delete result[kebabCaseKey]; } else { - result[key] = value; + result[kebabCaseKey] = value; } } } From a1dc547dc41b05092f1bcd733d4bb7602ea6c155 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 10 May 2024 14:49:40 +0200 Subject: [PATCH 351/567] backend-app-api: csp changeset Signed-off-by: Vincenzo Scamporlino --- .changeset/olive-pants-leave.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/olive-pants-leave.md diff --git a/.changeset/olive-pants-leave.md b/.changeset/olive-pants-leave.md new file mode 100644 index 0000000000..e14cc69912 --- /dev/null +++ b/.changeset/olive-pants-leave.md @@ -0,0 +1,11 @@ +--- +'@backstage/backend-app-api': patch +--- + +Added support for camel case CSP directives in app-config. For example: + +```yaml +backend: + csp: + upgradeInsecureRequests: false +``` From e3ce775a1b9bceaa68cfec3badd7891cc032b407 Mon Sep 17 00:00:00 2001 From: Subburaj Jagadeesan Date: Wed, 10 Apr 2024 19:09:27 +0100 Subject: [PATCH 352/567] Added docs for template task-list & start-over options Signed-off-by: Subburaj Jagadeesan --- .../software-templates/template-start-over.png | Bin 0 -> 235157 bytes .../software-templates/template-task-list.png | Bin 0 -> 218907 bytes docs/features/software-templates/index.md | 14 ++++++++++++++ 3 files changed, 14 insertions(+) create mode 100644 docs/assets/software-templates/template-start-over.png create mode 100644 docs/assets/software-templates/template-task-list.png diff --git a/docs/assets/software-templates/template-start-over.png b/docs/assets/software-templates/template-start-over.png new file mode 100644 index 0000000000000000000000000000000000000000..86ae13f12e828ef9bbcf9adaa357a3e4060c6c75 GIT binary patch literal 235157 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

    lg1d?4L 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

    3Pg7-jU2))Z4|gQT)J~10T{G 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�=)^L6ucmjQvsI- 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 We%MZAj=bde{a|kWJ<06mqkjPrZySyP literal 0 HcmV?d00001 diff --git a/docs/assets/software-templates/template-task-list.png b/docs/assets/software-templates/template-task-list.png new file mode 100644 index 0000000000000000000000000000000000000000..1e5eaa4b487c42918dc9b0f42a0f7e358e2aa073 GIT binary patch literal 218907 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±1 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ÍnyQK~ zqSzc@W}h*`XS1dj#M2K?k+Mx)hwRpf+)!7>+-^zrBed3E!n}j|rRnW~(o)~X>(_oM z_$Wez@!F}{SrQF1N}I@D!c~A*;Rq<4M{>T#qFq-HLMgbjeJzxz#$B~(Bh2AuR}NBF z%Wm>x+A*qwE4zK0)YGxzNS~z-cOE7DT0xU!0%T>0iBAV2quz808FOtDHn1`@alg$iy%Lj!O5U2Zr zO$l}ShUw_w(yc8gmC%C!f9M;!-Wbmh&N`hU)?~Y!Yn&?vZ0wiMFy~~rS8mkb1MS8r zm6#vnt&8N{r2NcL!ZfCdm;?VCSK!=J8XQ^2`>11d(oTk|81ZL%({(IzjFL)07M8Be z6`NloA4+g9IPPxM5<|Rzo>107pN9-A`{|b#yYef8R$S<$z(h#*kXS*WG{10X7_c}{&+Bp zp4`UG;=(p~^(;ac7*b4a>+>}!d2@-y{Bq`NgDXD@hl+j~)CU5etR^#4C*Q60#%h02 zvh|Kr)DC#1h3Y&)^L$dp^jE>NQqmp8`$MAZFs1p9Im3lFF;YeHP!tQ%%-TsYb|mh! z&3t8WSABdokY8392n0@aiu0}G9=?JR^qTs!DD!;s&$=+c70oN}4xH_t0GVYqq)0@} zzlA@g_nfRt8C9vrLr4@68pNU&4yeI=4nZi#BJwujN{`(7@@XxU6O3Uq;1K~yb8>~+G&RH7p(Pwq1Z#}5az6tr$|e# R8p`lRni?Zc5l>vZ_g}TPT66#a literal 0 HcmV?d00001 diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index d0c1bea9b2..a2df6cfe1c 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -103,3 +103,17 @@ from `backstage/packages/app/src/App.tsx`: ``` After the change, you should no longer see the button. + +## Previewing and Executing Previous Template Tasks + +Each execution of a template is treated as a unique task, identifiable by its own unique ID. To view a list of previously executed template tasks, navigate to the `Create` page and access the `Task List` from the context menu (represented by the vertical ellipsis, or 'kebab menu', icon in the upper right corner). + +![Template Task List](../../assets/software-templates/template-task-list.png) + +If you wish to re-run a previously executed template, navigate to the template tasks page. Locate the desired task and select the `Start Over` option from the context menu. + +![Template Start Over](../../assets/software-templates/template-start-over.png) + +This action will initiate a new execution of the selected template, pre-populated with the same parameters as the previous run, but these parameters can be edited before re-execution. + +In the event of a failed template execution, the `Start Over` option can be used to re-execute the template. The parameters from the original run will be pre-filled, but they can be adjusted as needed before retrying the template. From 3009021821216b78482c8580183385c13bacba97 Mon Sep 17 00:00:00 2001 From: Subburaj Jagadeesan Date: Wed, 8 May 2024 09:38:04 +0100 Subject: [PATCH 353/567] Updated the screenshots with create-app template Signed-off-by: Subburaj Jagadeesan --- .../template-start-over.png | Bin 235157 -> 208076 bytes .../software-templates/template-task-list.png | Bin 218907 -> 173816 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/assets/software-templates/template-start-over.png b/docs/assets/software-templates/template-start-over.png index 86ae13f12e828ef9bbcf9adaa357a3e4060c6c75..5e47540feb123c8dad9c11b533bda654dedc664e 100644 GIT binary patch literal 208076 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�A@OE{Znecz#8BL3Mu9y-R> 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 C-*`;` literal 235157 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

    lg1d?4L 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

    3Pg7-jU2))Z4|gQT)J~10T{G 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�=)^L6ucmjQvsI- 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 We%MZAj=bde{a|kWJ<06mqkjPrZySyP diff --git a/docs/assets/software-templates/template-task-list.png b/docs/assets/software-templates/template-task-list.png index 1e5eaa4b487c42918dc9b0f42a0f7e358e2aa073..b160003375d73bee010f998964281a0b5b04dcab 100644 GIT binary patch literal 173816 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±1 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ÍnyQK~ zqSzc@W}h*`XS1dj#M2K?k+Mx)hwRpf+)!7>+-^zrBed3E!n}j|rRnW~(o)~X>(_oM z_$Wez@!F}{SrQF1N}I@D!c~A*;Rq<4M{>T#qFq-HLMgbjeJzxz#$B~(Bh2AuR}NBF z%Wm>x+A*qwE4zK0)YGxzNS~z-cOE7DT0xU!0%T>0iBAV2quz808FOtDHn1`@alg$iy%Lj!O5U2Zr zO$l}ShUw_w(yc8gmC%C!f9M;!-Wbmh&N`hU)?~Y!Yn&?vZ0wiMFy~~rS8mkb1MS8r zm6#vnt&8N{r2NcL!ZfCdm;?VCSK!=J8XQ^2`>11d(oTk|81ZL%({(IzjFL)07M8Be z6`NloA4+g9IPPxM5<|Rzo>107pN9-A`{|b#yYef8R$S<$z(h#*kXS*WG{10X7_c}{&+Bp zp4`UG;=(p~^(;ac7*b4a>+>}!d2@-y{Bq`NgDXD@hl+j~)CU5etR^#4C*Q60#%h02 zvh|Kr)DC#1h3Y&)^L$dp^jE>NQqmp8`$MAZFs1p9Im3lFF;YeHP!tQ%%-TsYb|mh! z&3t8WSABdokY8392n0@aiu0}G9=?JR^qTs!DD!;s&$=+c70oN}4xH_t0GVYqq)0@} zzlA@g_nfRt8C9vrLr4@68pNU&4yeI=4nZi#BJwujN{`(7@@XxU6O3Uq;1K~yb8>~+G&RH7p(Pwq1Z#}5az6tr$|e# R8p`lRni?Zc5l>vZ_g}TPT66#a From beef2a1c9fd131b55d39de67c56e7f4ad588c5c1 Mon Sep 17 00:00:00 2001 From: Subburaj Jagadeesan Date: Thu, 9 May 2024 19:49:17 +0100 Subject: [PATCH 354/567] Updating the screenshots using create-app template Signed-off-by: Subburaj Jagadeesan --- .../template-start-over.png | Bin 208076 -> 206188 bytes .../software-templates/template-task-list.png | Bin 173816 -> 169496 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/assets/software-templates/template-start-over.png b/docs/assets/software-templates/template-start-over.png index 5e47540feb123c8dad9c11b533bda654dedc664e..67d9caff34ab9b3a2cb13295c21262b729b9988e 100644 GIT binary patch literal 206188 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 z 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�A@OE{Znecz#8BL3Mu9y-R> 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 C-*`;` diff --git a/docs/assets/software-templates/template-task-list.png b/docs/assets/software-templates/template-task-list.png index b160003375d73bee010f998964281a0b5b04dcab..dc2bbfa3102e32d2723c7d69e3887614e9084db6 100644 GIT binary patch literal 169496 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%0A`Y_V<+&QV(&&U&HB^rV-M8<` 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/o3DZHpU?_24cw!Td?9;>e;#$Du{@SclwQtzKiz0&KB0zUr}GJvx! zR!j84$CJ6Lkx|jt;3_|xg1hKWhJ)%!^05niV?7(AY}a{DtqV+XrVVs{rfE3K%0teu z0Q^?AdqJXLFd+XAdbSy(4q9O*eJ@h6fcmzmmmM29g$ihdA<&t*R|zr>ww#QAb61f+ zpx65C9QHdk6329tmGBTcg+la9DuSuoF>Ubg8UAdFFW{s%OdU68$H>V-psjAQNgoTo_=Q|`xYADtAtv8230kpu!viLc zfqs2t;n>lWphY`=vG3=h<*jjMvG27CqNgY*Zt;08U8kZm1q+(cz{OpsK~}5dxw<8@ z;L=4X$au+YnNMNw4)?hXxQ0p}T{2vSUud&wK$j5v{iS0vIBhFX&to*X%aSs&scZZY zbJ6hYeFCGxv(ooR_0xuMfoiyL_4JSrLep@6S(E1HBG}!w$nb1_m^D0R61nu)umAfl z;AXJ%KahsFa8kX0X=p^;4OB9Gkdrc&yl8f&c8rN!Yd|lMa71_tV@Y?#f3*SDDPyaC zpto*W&$XU=TgTmq2VC6tFK&34;66=5g0+Ibn2uktaqv0+ zUee~masS%VdUwiyM@VC#Y5ab?TZq-brTx#16^r%r?+6`&Fy7+He>*C_yUTag^FL&_ z7aQZhbSQXt%6EPIH!jCJZV*^p$d~9H$tv@K z0MfdL_Q?BPfsX&!GQY5m{TW#F7T~l zN-r(0!(G2`f zj*Z<|Bx-uCV~Ig1RSOU$N%ANyr3?YxkT&}RbQzTrMtmzfbxhjaKPv+{Wrgv{ynY_I z-Gs^9z%l}WB9TWZF-Ro?OY>)ECTaMgp}#h|tdI+KYASfc9Quh$!t!IAJt{|NpahnW zLn+Y(TLxn#5LtAC!29QX0b!G!y0o=er+S`^uVuV#mB!SyQF0ZGuCB;xNSf2gpW;7g zi5q|H>>!!Gm0bp>Sz#(qtKc$-R7nLGn7o2d>vR77bb8LiT-v%B-+3%ShQ-|3DBsm_ zu<`iY1wckmVqUg$bNt+$sOm{;GTI!mR1PjvfxZ8)6HbQ%MWhLx)0 z*$5hA&7<5_-4s_{D9P5%A$-ZWo~T~%=rjpaJAk+w<>3a}LB*oNPEXH+4EI`2aJj*y zg~kUO)orvY0zhmiRCAY*3kzYJCT3rP#c~!R!r;*zUDv8?38@1~+{5({Ow#<^72g%pLF&`m4s8Q^W$0NHKPO!#TPQ|2HPIdJk3`=bll7H>&wTD#zWsKQ9AX6jNtP^Vuv zm4Q~YZ3S3)oC||9k1v07#jOvxaa^sG!eaR$ki3hFi*v#C2OzKTN5G#y1^QQSW~va) zKk79^L7BSpWuD{p%*9vV4v@om0uh?_gAq9%T@pY literal 173816 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 Date: Fri, 10 May 2024 15:38:18 +0100 Subject: [PATCH 355/567] Run yarn prettier Signed-off-by: Mihai Tabara --- OWNERS.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/OWNERS.md b/OWNERS.md index 8385813411..cf3c26d8c6 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -22,15 +22,15 @@ Team: @backstage/catalog-maintainers Scope: The catalog plugin and catalog model -| Name | Organization | Team | GitHub | Discord | -| -------------------- | ------------ | --------------- | ----------------------------------------------- | ----------------| -| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | -| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | -| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | -| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | -| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | -| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | -| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | ### Discoverability @@ -74,15 +74,15 @@ Team: @backstage/permission-maintainers Scope: The Permission Framework and plugins integrating with the permission framework -| Name | Organization | Team | GitHub | Discord | -| -------------------- | ------------ | --------------- | ----------------------------------------------- | ----------------| -| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | -| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | -| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | -| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | -| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | -| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | -| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | ### TechDocs From 04368c42fede51d51d62e4e62f69cc120650ca89 Mon Sep 17 00:00:00 2001 From: Subburaj Jagadeesan Date: Fri, 10 May 2024 11:30:13 -0400 Subject: [PATCH 356/567] Update docs/features/software-templates/index.md Signed-off-by: Subburaj Jagadeesan Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- docs/features/software-templates/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index a2df6cfe1c..9642214ede 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -106,14 +106,14 @@ After the change, you should no longer see the button. ## Previewing and Executing Previous Template Tasks -Each execution of a template is treated as a unique task, identifiable by its own unique ID. To view a list of previously executed template tasks, navigate to the `Create` page and access the `Task List` from the context menu (represented by the vertical ellipsis, or 'kebab menu', icon in the upper right corner). +Each execution of a template is treated as a unique task, identifiable by its own unique ID. To view a list of previously executed template tasks, navigate to the "Create" page and access the "Task List" from the context menu (represented by the vertical ellipsis, or 'kebab menu', icon in the upper right corner). ![Template Task List](../../assets/software-templates/template-task-list.png) -If you wish to re-run a previously executed template, navigate to the template tasks page. Locate the desired task and select the `Start Over` option from the context menu. +If you wish to re-run a previously executed template, navigate to the template tasks page. Locate the desired task and select the "Start Over" option from the context menu. ![Template Start Over](../../assets/software-templates/template-start-over.png) This action will initiate a new execution of the selected template, pre-populated with the same parameters as the previous run, but these parameters can be edited before re-execution. -In the event of a failed template execution, the `Start Over` option can be used to re-execute the template. The parameters from the original run will be pre-filled, but they can be adjusted as needed before retrying the template. +In the event of a failed template execution, the "Start Over" option can be used to re-execute the template. The parameters from the original run will be pre-filled, but they can be adjusted as needed before retrying the template. From 8c8d36b967fcf15b6fa1d9deb05ab03ce7732592 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 10 May 2024 20:02:03 +0200 Subject: [PATCH 357/567] docs: improve github auth providers Signed-off-by: Vincenzo Scamporlino --- docs/auth/github/provider.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/auth/github/provider.md b/docs/auth/github/provider.md index 2c3aaeaf7d..4accc7841b 100644 --- a/docs/auth/github/provider.md +++ b/docs/auth/github/provider.md @@ -43,13 +43,12 @@ auth: # enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL} signIn: resolvers: - # typically you would pick one of these - - resolver: emailMatchingUserEntityProfileEmail - - resolver: emailLocalPartMatchingUserEntityName + # Matches the GitHub username with the Backstage user entity name. + # See https://backstage.io/docs/auth/github/provider#resolvers for more resolvers. - resolver: usernameMatchingUserEntityName ``` -The GitHub provider is a structure with three configuration keys: +The GitHub provider is a structure with five configuration keys: - `clientId`: The client ID that you generated on GitHub, e.g. `b59241722e3c3b4816e2` @@ -60,6 +59,9 @@ The GitHub provider is a structure with three configuration keys: initiating an OAuth flow, e.g. `https://your-intermediate-service.com/handler`. Only needed if Backstage is not the immediate receiver (e.g. one OAuth app for many backstage instances). +- `signIn`: The configuration for the sign-in process, including the **resolvers** + that should be used to match the user from the auth provider with the user + entity in the Backstage catalog (typically a single resolver is sufficient). ### Resolvers @@ -69,7 +71,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. From af207a6841ac54506be020cf7d59cfe03f8c3d25 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 18:24:52 +0000 Subject: [PATCH 358/567] chore(deps): update actions/checkout action to v4.1.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes.yml | 2 +- .github/workflows/automate_changeset_feedback.yml | 2 +- .github/workflows/automate_merge_message.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/deploy_docker-image.yml | 2 +- .github/workflows/deploy_microsite.yml | 2 +- .github/workflows/deploy_nightly.yml | 2 +- .github/workflows/deploy_packages.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_code-formatting.yml | 2 +- .github/workflows/sync_dependabot-changesets.yml | 2 +- .github/workflows/sync_release-manifest.yml | 4 ++-- .github/workflows/sync_renovate-changesets.yml | 2 +- .github/workflows/sync_snyk-github-issues.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/sync_version-packages.yml | 2 +- .github/workflows/uffizzi-build.yml | 4 ++-- .github/workflows/verify_accessibility.yml | 2 +- .github/workflows/verify_codeql.yml | 2 +- .github/workflows/verify_docs-quality.yml | 2 +- .github/workflows/verify_e2e-kubernetes.yml | 2 +- .github/workflows/verify_e2e-linux.yml | 2 +- .github/workflows/verify_e2e-techdocs.yml | 2 +- .github/workflows/verify_e2e-windows.yml | 2 +- .github/workflows/verify_fossa.yml | 2 +- .github/workflows/verify_microsite.yml | 2 +- .github/workflows/verify_microsite_accessibility.yml | 2 +- .github/workflows/verify_storybook.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- 29 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 84161e3010..5f456a9b0b 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index ef1a258239..6ac327bcd6 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index dd8895a752..56ab3283ac 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c538ec8957..ebe1dab5b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -68,7 +68,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -197,7 +197,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: fetch master branch run: git fetch origin master diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index d5f5eee35e..19c58a8a60 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -25,7 +25,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 037fb27e8d..2554e3eeb8 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index ab8ea3c773..6ec5b03d56 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 44222275ad..444f3f825d 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -65,7 +65,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -148,7 +148,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 8a069d476d..0b883dd75a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -34,7 +34,7 @@ jobs: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: persist-credentials: false diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 7f10ae60df..dce7217201 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -14,7 +14,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 7c8e634fb8..ddd60750d1 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 7fe8817731..c971be522f 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -21,7 +21,7 @@ jobs: run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1 - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: path: backstage # 'v' prefix is added here for the tag, we keep it out of the manifest logic @@ -29,7 +29,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: repository: backstage/versions path: backstage/versions diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index e1c5656d08..9d031812ea 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 1948807215..2ea3fcd5a1 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 43c613b641..e6b274c395 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -29,7 +29,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master with: diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index ed69fd3c24..884bcf3d95 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: fetch-depth: 20000 fetch-tags: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index eab0a61e46..5ab208590a 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: setup-node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -89,7 +89,7 @@ jobs: egress-policy: audit - name: Checkout git repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Render Compose File run: | # update image after the build above diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 2c0e10c118..75415f7961 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -24,7 +24,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index f028147b85..0c30218d72 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -47,7 +47,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 21d3563b57..f784608493 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 # Vale does not support file excludes, so we use the script to generate a list of files instead # The action also does not allow args or a local config file to be passed in, so the files array diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index ffaa7971e6..9763cbd085 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -26,7 +26,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 6ff3b9eecf..b9229263ae 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -45,7 +45,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Configure Git run: | diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index ad0950392f..31d9d8b06a 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -34,7 +34,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 7a823a85e1..064baf700e 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -42,7 +42,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Configure Git run: | diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 4c9b1516a2..152fe9f185 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -19,7 +19,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 724fac7fef..48d7da218d 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index af2364e743..9728bf57c6 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 3cdb0de344..9a59b7b071 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 with: fetch-depth: 0 # Required to retrieve git history diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 0c54cfb287..cd7a9b62ae 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -33,7 +33,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 From fa9def08249b0566b51a9528ce0a6e158e37dbfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 10 May 2024 20:30:21 +0200 Subject: [PATCH 359/567] Update packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../src/components/OverflowTooltip/OverflowTooltip.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index f0799b0d69..4b5b0f4bde 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -34,7 +34,6 @@ const useStyles = makeStyles( overflow: 'visible !important', }, typo: { - maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', From 76999670d79ba766efe931b6ef38d530335b64bb Mon Sep 17 00:00:00 2001 From: Elias Rieb Date: Tue, 7 May 2024 16:44:14 +0200 Subject: [PATCH 360/567] catalog: use LoggerService instead of winston Logger in ldap module Signed-off-by: Elias Rieb --- .changeset/clean-pants-camp.md | 5 +++++ .../catalog-backend-module-ldap/api-report.md | 18 +++++++++--------- .../catalog-backend-module-ldap/package.json | 4 ++-- .../src/ldap/client.ts | 6 +++--- .../src/ldap/read.ts | 4 ++-- .../src/processors/LdapOrgEntityProvider.ts | 10 +++++----- .../src/processors/LdapOrgReaderProcessor.ts | 8 ++++---- yarn.lock | 2 +- 8 files changed, 31 insertions(+), 26 deletions(-) create mode 100644 .changeset/clean-pants-camp.md diff --git a/.changeset/clean-pants-camp.md b/.changeset/clean-pants-camp.md new file mode 100644 index 0000000000..2070305a6d --- /dev/null +++ b/.changeset/clean-pants-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Remove dependency to Winston Logger and use Backstage LoggerService instead diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index 277a48af8d..76276c8fad 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -12,7 +12,7 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { GroupEntity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; import { LocationSpec } from '@backstage/plugin-catalog-common'; -import { Logger } from 'winston'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { SearchEntry } from 'ldapjs'; import { SearchOptions } from 'ldapjs'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -76,10 +76,10 @@ export const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid'; // @public export class LdapClient { - constructor(client: Client, logger: Logger); + constructor(client: Client, logger: LoggerService); // (undocumented) static create( - logger: Logger, + logger: LoggerService, target: string, bind?: BindConfig, tls?: TLSConfig, @@ -99,7 +99,7 @@ export class LdapOrgEntityProvider implements EntityProvider { constructor(options: { id: string; provider: LdapProviderConfig; - logger: Logger; + logger: LoggerService; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; }); @@ -112,14 +112,14 @@ export class LdapOrgEntityProvider implements EntityProvider { ): LdapOrgEntityProvider; // (undocumented) getProviderName(): string; - read(options?: { logger?: Logger }): Promise; + read(options?: { logger?: LoggerService }): Promise; } // @public export interface LdapOrgEntityProviderOptions { groupTransformer?: GroupTransformer; id: string; - logger: Logger; + logger: LoggerService; schedule: 'manual' | TaskRunner; target: string; userTransformer?: UserTransformer; @@ -129,7 +129,7 @@ export interface LdapOrgEntityProviderOptions { export class LdapOrgReaderProcessor implements CatalogProcessor { constructor(options: { providers: LdapProviderConfig[]; - logger: Logger; + logger: LoggerService; groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; }); @@ -137,7 +137,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { static fromConfig( configRoot: Config, options: { - logger: Logger; + logger: LoggerService; groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; }, @@ -187,7 +187,7 @@ export function readLdapOrg( options: { groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; - logger: Logger; + logger: LoggerService; }, ): Promise<{ users: UserEntity[]; diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index e50d629e0e..2be1113e64 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -36,6 +36,7 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", @@ -46,8 +47,7 @@ "@types/ldapjs": "^2.2.5", "ldapjs": "^2.3.3", "lodash": "^4.17.21", - "uuid": "^9.0.0", - "winston": "^3.2.1" + "uuid": "^9.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 75e54013b3..48a3eedfb8 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -19,7 +19,6 @@ import { readFile } from 'fs/promises'; import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs'; import { cloneDeep } from 'lodash'; import tlsLib from 'tls'; -import { Logger } from 'winston'; import { BindConfig, TLSConfig } from './config'; import { createOptions, errorString } from './util'; import { @@ -29,6 +28,7 @@ import { FreeIpaVendor, LdapVendor, } from './vendors'; +import { LoggerService } from '@backstage/backend-plugin-api'; /** * Basic wrapper for the `ldapjs` library. @@ -41,7 +41,7 @@ export class LdapClient { private vendor: Promise | undefined; static async create( - logger: Logger, + logger: LoggerService, target: string, bind?: BindConfig, tls?: TLSConfig, @@ -89,7 +89,7 @@ export class LdapClient { constructor( private readonly client: Client, - private readonly logger: Logger, + private readonly logger: LoggerService, ) {} /** diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 590d27ba8b..50036b94b0 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -31,9 +31,9 @@ import { LDAP_UUID_ANNOTATION, } from './constants'; import { LdapVendor } from './vendors'; -import { Logger } from 'winston'; import { GroupTransformer, UserTransformer } from './types'; import { mapStringAttr } from './util'; +import { LoggerService } from '@backstage/backend-plugin-api'; /** * The default implementation of the transformation from an LDAP entry to a @@ -269,7 +269,7 @@ export async function readLdapOrg( options: { groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; - logger: Logger; + logger: LoggerService; }, ): Promise<{ users: UserEntity[]; diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts index d4f19d0522..97ea4a53b0 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts @@ -27,7 +27,6 @@ import { } from '@backstage/plugin-catalog-node'; import { merge } from 'lodash'; import * as uuid from 'uuid'; -import { Logger } from 'winston'; import { GroupTransformer, LdapClient, @@ -37,6 +36,7 @@ import { readLdapOrg, UserTransformer, } from '../ldap'; +import { LoggerService } from '@backstage/backend-plugin-api'; /** * Options for {@link LdapOrgEntityProvider}. @@ -64,7 +64,7 @@ export interface LdapOrgEntityProviderOptions { /** * The logger to use. */ - logger: Logger; + logger: LoggerService; /** * The refresh schedule to use. @@ -143,7 +143,7 @@ export class LdapOrgEntityProvider implements EntityProvider { private options: { id: string; provider: LdapProviderConfig; - logger: Logger; + logger: LoggerService; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; }, @@ -164,7 +164,7 @@ export class LdapOrgEntityProvider implements EntityProvider { * Runs one single complete ingestion. This is only necessary if you use * manual scheduling. */ - async read(options?: { logger?: Logger }) { + async read(options?: { logger?: LoggerService }) { if (!this.connection) { throw new Error('Not initialized'); } @@ -237,7 +237,7 @@ export class LdapOrgEntityProvider implements EntityProvider { } // Helps wrap the timing and logging behaviors -function trackProgress(logger: Logger) { +function trackProgress(logger: LoggerService) { let timestamp = Date.now(); let summary: string; diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index 00578ac3a4..c920968fc1 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -15,7 +15,6 @@ */ import { Config } from '@backstage/config'; -import { Logger } from 'winston'; import { GroupTransformer, LdapClient, @@ -30,6 +29,7 @@ import { processingResult, } from '@backstage/plugin-catalog-node'; import { LocationSpec } from '@backstage/plugin-catalog-common'; +import { LoggerService } from '@backstage/backend-plugin-api'; /** * Extracts teams and users out of an LDAP server. @@ -38,14 +38,14 @@ import { LocationSpec } from '@backstage/plugin-catalog-common'; */ export class LdapOrgReaderProcessor implements CatalogProcessor { private readonly providers: LdapProviderConfig[]; - private readonly logger: Logger; + private readonly logger: LoggerService; private readonly groupTransformer?: GroupTransformer; private readonly userTransformer?: UserTransformer; static fromConfig( configRoot: Config, options: { - logger: Logger; + logger: LoggerService; groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; }, @@ -62,7 +62,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { constructor(options: { providers: LdapProviderConfig[]; - logger: Logger; + logger: LoggerService; groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; }) { diff --git a/yarn.lock b/yarn.lock index 6cee25b42b..160b41ceec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5269,6 +5269,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-backend-module-ldap@workspace:plugins/catalog-backend-module-ldap" dependencies: + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" @@ -5282,7 +5283,6 @@ __metadata: ldapjs: ^2.3.3 lodash: ^4.17.21 uuid: ^9.0.0 - winston: ^3.2.1 languageName: unknown linkType: soft From e42779e9b86ce1d27870d00aa37491278b84f94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 13 May 2024 09:31:37 +0200 Subject: [PATCH 361/567] Properly log the errorInfo in ErrorBoundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/cool-garlics-clap.md | 5 +++++ .../src/layout/ErrorBoundary/ErrorBoundary.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/cool-garlics-clap.md diff --git a/.changeset/cool-garlics-clap.md b/.changeset/cool-garlics-clap.md new file mode 100644 index 0000000000..223d0a1233 --- /dev/null +++ b/.changeset/cool-garlics-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Properly log the `errorInfo` in `ErrorBoundary` diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx index 8f746882a1..b3a5d1db90 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx @@ -79,7 +79,7 @@ export const ErrorBoundary: ComponentClass< componentDidCatch(error: Error, errorInfo: ErrorInfo) { // eslint-disable-next-line no-console - console.error(`ErrorBoundary, error: ${error}, info: ${errorInfo}`); + console.error(`ErrorBoundary, error: ${error}`, { error, errorInfo }); this.setState({ error, errorInfo }); } From 57f692e66ca58ca4a3ab29dbbff237770a49f46d Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 3 May 2024 09:25:34 +0200 Subject: [PATCH 362/567] refactor(backend-common): deprecate isomorphic git class Signed-off-by: Camila Belo --- .changeset/famous-crabs-laugh.md | 6 + packages/backend-common/api-report.md | 6 +- .../backend-common/src/deprecated/index.ts | 17 + .../src/{ => deprecated}/scm/git.test.ts | 0 .../backend-common/src/deprecated/scm/git.ts | 357 +++++++++++ .../src/{ => deprecated}/scm/index.ts | 0 packages/backend-common/src/index.ts | 3 +- .../src/reading/GerritUrlReader.test.ts | 2 +- .../src/reading/GerritUrlReader.ts | 28 +- .../src/reading/GiteaUrlReader.test.ts | 2 +- .../src/reading/HarnessUrlReader.test.ts | 2 +- packages/backend-common/src/reading/git.ts | 143 +++++ plugins/scaffolder-node/package.json | 1 + .../src/actions/gitHelpers.test.ts | 5 +- .../scaffolder-node/src/actions/gitHelpers.ts | 2 +- plugins/scaffolder-node/src/scm/git.test.ts | 606 ++++++++++++++++++ .../scaffolder-node}/src/scm/git.ts | 0 plugins/scaffolder-node/src/scm/index.ts | 18 + yarn.lock | 1 + 19 files changed, 1178 insertions(+), 21 deletions(-) create mode 100644 .changeset/famous-crabs-laugh.md create mode 100644 packages/backend-common/src/deprecated/index.ts rename packages/backend-common/src/{ => deprecated}/scm/git.test.ts (100%) create mode 100644 packages/backend-common/src/deprecated/scm/git.ts rename packages/backend-common/src/{ => deprecated}/scm/index.ts (100%) create mode 100644 packages/backend-common/src/reading/git.ts create mode 100644 plugins/scaffolder-node/src/scm/git.test.ts rename {packages/backend-common => plugins/scaffolder-node}/src/scm/git.ts (100%) create mode 100644 plugins/scaffolder-node/src/scm/index.ts diff --git a/.changeset/famous-crabs-laugh.md b/.changeset/famous-crabs-laugh.md new file mode 100644 index 0000000000..6427fbbc82 --- /dev/null +++ b/.changeset/famous-crabs-laugh.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': patch +'@backstage/plugin-scaffolder-node': patch +--- + +Preparing for a stable new backend system release, we are deprecating utilities in the `backend-common` that are not used by the core framework, such as the isomorphic `Git` class. As we will no longer support the isomorphic `Git` utility in the framework packages, we recommend plugins that start maintaining their own implementation of this class. diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index cccf326b9f..3a04072499 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -71,7 +71,7 @@ import { V1PodTemplateSpec } from '@kubernetes/client-node'; import * as winston from 'winston'; import { Writable } from 'stream'; -// @public +// @public @deprecated export type AuthCallbackOptions = { onAuth: AuthCallback; logger?: LoggerService; @@ -370,7 +370,7 @@ export function getRootLogger(): winston.Logger; // @public export function getVoidLogger(): winston.Logger; -// @public +// @public @deprecated export class Git { // (undocumented) add(options: { dir: string; filepath: string }): Promise; @@ -818,7 +818,7 @@ export function setRootLogger(newLogger: winston.Logger): void; // @public @deprecated export const SingleHostDiscovery: typeof HostDiscovery_2; -// @public +// @public @deprecated export type StaticAuthOptions = { username?: string; password?: string; diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts new file mode 100644 index 0000000000..ed5fdcc577 --- /dev/null +++ b/packages/backend-common/src/deprecated/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './scm'; diff --git a/packages/backend-common/src/scm/git.test.ts b/packages/backend-common/src/deprecated/scm/git.test.ts similarity index 100% rename from packages/backend-common/src/scm/git.test.ts rename to packages/backend-common/src/deprecated/scm/git.test.ts diff --git a/packages/backend-common/src/deprecated/scm/git.ts b/packages/backend-common/src/deprecated/scm/git.ts new file mode 100644 index 0000000000..1a182e6f4d --- /dev/null +++ b/packages/backend-common/src/deprecated/scm/git.ts @@ -0,0 +1,357 @@ +/* + * 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 git, { + ProgressCallback, + MergeResult, + ReadCommitResult, + AuthCallback, +} from 'isomorphic-git'; +import http from 'isomorphic-git/http/node'; +import fs from 'fs-extra'; +import { LoggerService } from '@backstage/backend-plugin-api'; + +function isAuthCallbackOptions( + options: StaticAuthOptions | AuthCallbackOptions, +): options is AuthCallbackOptions { + return 'onAuth' in options; +} + +/** + * Configure static credential for authentication + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + */ +export type StaticAuthOptions = { + username?: string; + password?: string; + token?: string; + logger?: LoggerService; +}; + +/** + * Configure an authentication callback that can provide credentials on demand + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + */ +export type AuthCallbackOptions = { + onAuth: AuthCallback; + logger?: LoggerService; +}; + +/* +provider username password +Azure 'notempty' token +Bitbucket Cloud 'x-token-auth' token +Bitbucket Server username password or token +GitHub 'x-access-token' token +GitLab 'oauth2' token + +From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub + +Or token provided as `token` for Bearer auth header +instead of Basic Auth (e.g., Bitbucket Server). +*/ + +/** + * A convenience wrapper around the `isomorphic-git` library. + * @public + * @deprecated This class is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + */ + +export class Git { + private readonly headers: { + [x: string]: string; + }; + + private constructor( + private readonly config: { + onAuth: AuthCallback; + token?: string; + logger?: LoggerService; + }, + ) { + this.onAuth = config.onAuth; + + this.headers = { + 'user-agent': 'git/@isomorphic-git', + ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}), + }; + } + + async add(options: { dir: string; filepath: string }): Promise { + const { dir, filepath } = options; + this.config.logger?.info(`Adding file {dir=${dir},filepath=${filepath}}`); + + return git.add({ fs, dir, filepath }); + } + + async addRemote(options: { + dir: string; + remote: string; + url: string; + force?: boolean; + }): Promise { + const { dir, url, remote, force } = options; + this.config.logger?.info( + `Creating new remote {dir=${dir},remote=${remote},url=${url}}`, + ); + return git.addRemote({ fs, dir, remote, url, force }); + } + + async deleteRemote(options: { dir: string; remote: string }): Promise { + const { dir, remote } = options; + this.config.logger?.info(`Deleting remote {dir=${dir},remote=${remote}}`); + return git.deleteRemote({ fs, dir, remote }); + } + + async checkout(options: { dir: string; ref: string }): Promise { + const { dir, ref } = options; + this.config.logger?.info(`Checking out branch {dir=${dir},ref=${ref}}`); + + return git.checkout({ fs, dir, ref }); + } + + async branch(options: { dir: string; ref: string }): Promise { + const { dir, ref } = options; + this.config.logger?.info(`Creating branch {dir=${dir},ref=${ref}`); + + return git.branch({ fs, dir, ref }); + } + + async commit(options: { + dir: string; + message: string; + author: { name: string; email: string }; + committer: { name: string; email: string }; + }): Promise { + const { dir, message, author, committer } = options; + this.config.logger?.info( + `Committing file to repo {dir=${dir},message=${message}}`, + ); + return git.commit({ fs, dir, message, author, committer }); + } + + /** https://isomorphic-git.org/docs/en/clone */ + async clone(options: { + url: string; + dir: string; + ref?: string; + depth?: number; + noCheckout?: boolean; + }): Promise { + const { url, dir, ref, depth, noCheckout } = options; + this.config.logger?.info(`Cloning repo {dir=${dir},url=${url}}`); + + try { + return await git.clone({ + fs, + http, + url, + dir, + ref, + singleBranch: true, + depth: depth ?? 1, + noCheckout, + onProgress: this.onProgressHandler(), + headers: this.headers, + onAuth: this.onAuth, + }); + } catch (ex) { + this.config.logger?.error(`Failed to clone repo {dir=${dir},url=${url}}`); + if (ex.data) { + throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`); + } + throw ex; + } + } + + /** https://isomorphic-git.org/docs/en/currentBranch */ + async currentBranch(options: { + dir: string; + fullName?: boolean; + }): Promise { + const { dir, fullName = false } = options; + return git.currentBranch({ fs, dir, fullname: fullName }) as Promise< + string | undefined + >; + } + + /** https://isomorphic-git.org/docs/en/fetch */ + async fetch(options: { + dir: string; + remote?: string; + tags?: boolean; + }): Promise { + const { dir, remote = 'origin', tags = false } = options; + this.config.logger?.info( + `Fetching remote=${remote} for repository {dir=${dir}}`, + ); + + try { + await git.fetch({ + fs, + http, + dir, + remote, + tags, + onProgress: this.onProgressHandler(), + headers: this.headers, + onAuth: this.onAuth, + }); + } catch (ex) { + this.config.logger?.error( + `Failed to fetch repo {dir=${dir},remote=${remote}}`, + ); + if (ex.data) { + throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`); + } + throw ex; + } + } + + async init(options: { dir: string; defaultBranch?: string }): Promise { + const { dir, defaultBranch = 'master' } = options; + this.config.logger?.info(`Init git repository {dir=${dir}}`); + + return git.init({ + fs, + dir, + defaultBranch, + }); + } + + /** https://isomorphic-git.org/docs/en/merge */ + async merge(options: { + dir: string; + theirs: string; + ours?: string; + author: { name: string; email: string }; + committer: { name: string; email: string }; + }): Promise { + const { dir, theirs, ours, author, committer } = options; + this.config.logger?.info( + `Merging branch '${theirs}' into '${ours}' for repository {dir=${dir}}`, + ); + + // If ours is undefined, current branch is used. + return git.merge({ + fs, + dir, + ours, + theirs, + author, + committer, + }); + } + + async push(options: { + dir: string; + remote: string; + remoteRef?: string; + force?: boolean; + }) { + const { dir, remote, remoteRef, force } = options; + this.config.logger?.info( + `Pushing directory to remote {dir=${dir},remote=${remote}}`, + ); + try { + return await git.push({ + fs, + dir, + http, + onProgress: this.onProgressHandler(), + remoteRef, + force, + headers: this.headers, + remote, + onAuth: this.onAuth, + }); + } catch (ex) { + this.config.logger?.error( + `Failed to push to repo {dir=${dir}, remote=${remote}}`, + ); + if (ex.data) { + throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`); + } + throw ex; + } + } + + /** https://isomorphic-git.org/docs/en/readCommit */ + async readCommit(options: { + dir: string; + sha: string; + }): Promise { + const { dir, sha } = options; + return git.readCommit({ fs, dir, oid: sha }); + } + + /** https://isomorphic-git.org/docs/en/remove */ + async remove(options: { dir: string; filepath: string }): Promise { + const { dir, filepath } = options; + this.config.logger?.info( + `Removing file from git index {dir=${dir},filepath=${filepath}}`, + ); + return git.remove({ fs, dir, filepath }); + } + + /** https://isomorphic-git.org/docs/en/resolveRef */ + async resolveRef(options: { dir: string; ref: string }): Promise { + const { dir, ref } = options; + return git.resolveRef({ fs, dir, ref }); + } + + /** https://isomorphic-git.org/docs/en/log */ + async log(options: { + dir: string; + ref?: string; + }): Promise { + const { dir, ref } = options; + return git.log({ + fs, + dir, + ref: ref ?? 'HEAD', + }); + } + + private onAuth: AuthCallback; + + private onProgressHandler = (): ProgressCallback => { + let currentPhase = ''; + + return event => { + if (currentPhase !== event.phase) { + currentPhase = event.phase; + this.config.logger?.info(event.phase); + } + const total = event.total + ? `${Math.round((event.loaded / event.total) * 100)}%` + : event.loaded; + this.config.logger?.debug(`status={${event.phase},total={${total}}}`); + }; + }; + + static fromAuth = (options: StaticAuthOptions | AuthCallbackOptions) => { + if (isAuthCallbackOptions(options)) { + const { onAuth, logger } = options; + return new Git({ onAuth, logger }); + } + + const { username, password, token, logger } = options; + return new Git({ onAuth: () => ({ username, password }), token, logger }); + }; +} diff --git a/packages/backend-common/src/scm/index.ts b/packages/backend-common/src/deprecated/scm/index.ts similarity index 100% rename from packages/backend-common/src/scm/index.ts rename to packages/backend-common/src/deprecated/scm/index.ts diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 4b1314b7d2..dfae1d549c 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -22,6 +22,8 @@ export { legacyPlugin, makeLegacyPlugin } from './legacy'; export type { LegacyCreateRouter } from './legacy'; +export { Git } from './deprecated'; +export type { StaticAuthOptions, AuthCallbackOptions } from './deprecated'; export * from './auth'; export * from './cache'; export { loadBackendConfig } from './config'; @@ -32,7 +34,6 @@ export * from './logging'; export * from './middleware'; export * from './paths'; export * from './reading'; -export * from './scm'; export * from './service'; export * from './tokens'; export * from './util'; diff --git a/packages/backend-common/src/reading/GerritUrlReader.test.ts b/packages/backend-common/src/reading/GerritUrlReader.test.ts index 66943cac51..14f96b529a 100644 --- a/packages/backend-common/src/reading/GerritUrlReader.test.ts +++ b/packages/backend-common/src/reading/GerritUrlReader.test.ts @@ -47,7 +47,7 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({ }); const cloneMock = jest.fn(() => Promise.resolve()); -jest.mock('../scm', () => ({ +jest.mock('./git', () => ({ Git: { fromAuth: () => ({ clone: cloneMock, diff --git a/packages/backend-common/src/reading/GerritUrlReader.ts b/packages/backend-common/src/reading/GerritUrlReader.ts index 76de711dec..b8a3d98392 100644 --- a/packages/backend-common/src/reading/GerritUrlReader.ts +++ b/packages/backend-common/src/reading/GerritUrlReader.ts @@ -14,7 +14,15 @@ * limitations under the License. */ -import { NotFoundError, NotModifiedError } from '@backstage/errors'; +import { Base64Decode } from 'base64-stream'; +import concatStream from 'concat-stream'; +import fs from 'fs-extra'; +import fetch, { Response } from 'node-fetch'; +import os from 'os'; +import { join as joinPath } from 'path'; +import { Readable, pipeline as pipelineCb } from 'stream'; +import tar from 'tar'; +import { promisify } from 'util'; import { GerritIntegration, ScmIntegrations, @@ -26,16 +34,7 @@ import { parseGerritGitilesUrl, parseGerritJsonResponse, } from '@backstage/integration'; -import { Base64Decode } from 'base64-stream'; -import concatStream from 'concat-stream'; -import fs from 'fs-extra'; -import fetch, { Response } from 'node-fetch'; -import os from 'os'; -import { join as joinPath } from 'path'; -import { Readable, pipeline as pipelineCb } from 'stream'; -import tar from 'tar'; -import { promisify } from 'util'; -import { Git } from '../scm'; +import { NotFoundError, NotModifiedError } from '@backstage/errors'; import { ReadTreeOptions, ReadTreeResponse, @@ -46,6 +45,13 @@ import { SearchResponse, UrlReader, } from './types'; +import { StaticAuthOptions, AuthCallbackOptions, Git } from './git'; + +export function isAuthCallbackOptions( + options: StaticAuthOptions | AuthCallbackOptions, +): options is AuthCallbackOptions { + return 'onAuth' in options; +} const pipeline = promisify(pipelineCb); diff --git a/packages/backend-common/src/reading/GiteaUrlReader.test.ts b/packages/backend-common/src/reading/GiteaUrlReader.test.ts index a941d2e5aa..f8658a2241 100644 --- a/packages/backend-common/src/reading/GiteaUrlReader.test.ts +++ b/packages/backend-common/src/reading/GiteaUrlReader.test.ts @@ -33,7 +33,7 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), }); -jest.mock('../scm', () => ({ +jest.mock('./git', () => ({ Git: { fromAuth: () => ({ clone: jest.fn(() => Promise.resolve({})), diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index bb09baa140..2741b75fd5 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -30,7 +30,7 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), }); -jest.mock('../scm', () => ({ +jest.mock('./git', () => ({ Git: { fromAuth: () => ({ clone: jest.fn(() => Promise.resolve({})), diff --git a/packages/backend-common/src/reading/git.ts b/packages/backend-common/src/reading/git.ts new file mode 100644 index 0000000000..ecd38d6c0c --- /dev/null +++ b/packages/backend-common/src/reading/git.ts @@ -0,0 +1,143 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import fs from 'fs-extra'; +import isomorphicGit, { ProgressCallback, AuthCallback } from 'isomorphic-git'; +import http from 'isomorphic-git/http/node'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { isAuthCallbackOptions } from './GerritUrlReader'; + +/** + * Configure static credential for authentication + * + * @public + */ +export type StaticAuthOptions = { + username?: string; + password?: string; + token?: string; + logger?: LoggerService; +}; + +/** + * Configure an authentication callback that can provide credentials on demand + * + * @public + */ +export type AuthCallbackOptions = { + onAuth: AuthCallback; + logger?: LoggerService; +}; + +/* +provider username password +Azure 'notempty' token +Bitbucket Cloud 'x-token-auth' token +Bitbucket Server username password or token +GitHub 'x-access-token' token +GitLab 'oauth2' token + +From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub + +Or token provided as `token` for Bearer auth header +instead of Basic Auth (e.g., Bitbucket Server). +*/ +/** + * A convenience wrapper around the `isomorphic-git` library. + * + * @public + */ +export class Git { + private readonly headers: { + [x: string]: string; + }; + + private constructor( + private readonly config: { + onAuth: AuthCallback; + token?: string; + logger?: LoggerService; + }, + ) { + this.onAuth = config.onAuth; + + this.headers = { + 'user-agent': 'git/@isomorphic-git', + ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}), + }; + } + + /** https://isomorphic-git.org/docs/en/clone */ + async clone(options: { + url: string; + dir: string; + ref?: string; + depth?: number; + noCheckout?: boolean; + }): Promise { + const { url, dir, ref, depth, noCheckout } = options; + this.config.logger?.info(`Cloning repo {dir=${dir},url=${url}}`); + + try { + return await isomorphicGit.clone({ + fs, + http, + url, + dir, + ref, + singleBranch: true, + depth: depth ?? 1, + noCheckout, + onProgress: this.onProgressHandler(), + headers: this.headers, + onAuth: this.onAuth, + }); + } catch (ex) { + this.config.logger?.error(`Failed to clone repo {dir=${dir},url=${url}}`); + if (ex.data) { + throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`); + } + throw ex; + } + } + + private onAuth: AuthCallback; + + private onProgressHandler = (): ProgressCallback => { + let currentPhase = ''; + + return event => { + if (currentPhase !== event.phase) { + currentPhase = event.phase; + this.config.logger?.info(event.phase); + } + const total = event.total + ? `${Math.round((event.loaded / event.total) * 100)}%` + : event.loaded; + this.config.logger?.debug(`status={${event.phase},total={${total}}}`); + }; + }; + + static fromAuth = (options: StaticAuthOptions | AuthCallbackOptions) => { + if (isAuthCallbackOptions(options)) { + const { onAuth, logger } = options; + return new Git({ onAuth, logger }); + } + + const { username, password, token, logger } = options; + return new Git({ onAuth: () => ({ username, password }), token, logger }); + }; +} diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 11d3da67bc..c45382a707 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -54,6 +54,7 @@ "@backstage/types": "workspace:^", "fs-extra": "^11.2.0", "globby": "^11.0.0", + "isomorphic-git": "^1.23.0", "jsonschema": "^1.2.6", "p-limit": "^3.1.0", "winston": "^3.2.1", diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts index b230b2cd06..64281d660f 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { Git, getVoidLogger } from '@backstage/backend-common'; +import { getVoidLogger } from '@backstage/backend-common'; +import { Git } from '../scm'; import { commitAndPushRepo, initRepoAndPush, @@ -24,7 +25,7 @@ import { cloneRepo, } from './gitHelpers'; -jest.mock('@backstage/backend-common', () => ({ +jest.mock('../scm', () => ({ Git: { fromAuth: jest.fn().mockReturnValue({ init: jest.fn(), diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.ts b/plugins/scaffolder-node/src/actions/gitHelpers.ts index 2026e75247..04f47d1648 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Git } from '@backstage/backend-common'; import { Logger } from 'winston'; +import { Git } from '../scm'; /** * @public diff --git a/plugins/scaffolder-node/src/scm/git.test.ts b/plugins/scaffolder-node/src/scm/git.test.ts new file mode 100644 index 0000000000..1c07dfb917 --- /dev/null +++ b/plugins/scaffolder-node/src/scm/git.test.ts @@ -0,0 +1,606 @@ +/* + * 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. + */ +jest.mock('isomorphic-git'); +jest.mock('isomorphic-git/http/node'); +jest.mock('fs-extra'); + +import * as isomorphic from 'isomorphic-git'; +import { Git } from './git'; +import http from 'isomorphic-git/http/node'; +import fs from 'fs-extra'; + +describe('Git', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('add', () => { + it('should call isomorphic-git add with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const filepath = 'mockfile/path'; + + await git.add({ dir, filepath }); + + expect(isomorphic.add).toHaveBeenCalledWith({ + fs, + dir, + filepath, + }); + }); + }); + + describe('addRemote', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const remote = 'origin'; + const url = 'git@github.com/something/sads'; + const force = true; + + await git.addRemote({ dir, remote, url, force }); + + expect(isomorphic.addRemote).toHaveBeenCalledWith({ + fs, + dir, + remote, + url, + force, + }); + }); + }); + + describe('remove', () => { + it('should call isomorphic-git remove with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const filepath = 'mockfile/path'; + + await git.remove({ dir, filepath }); + + expect(isomorphic.remove).toHaveBeenCalledWith({ + fs, + dir, + filepath, + }); + }); + }); + + describe('deleteRemote', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const remote = 'origin'; + + await git.deleteRemote({ dir, remote }); + + expect(isomorphic.deleteRemote).toHaveBeenCalledWith({ + fs, + dir, + remote, + }); + }); + }); + + describe('checkout', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const ref = 'master'; + + await git.checkout({ dir, ref }); + + expect(isomorphic.checkout).toHaveBeenCalledWith({ + fs, + dir, + ref, + }); + }); + }); + + describe('branch', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const ref = 'master'; + + await git.branch({ dir, ref }); + + expect(isomorphic.branch).toHaveBeenCalledWith({ + fs, + dir, + ref, + }); + }); + }); + + describe('commit', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const message = 'Inital Commit'; + const author = { + name: 'author', + email: 'test@backstage.io', + }; + const committer = { + name: 'comitter', + email: 'test@backstage.io', + }; + + await git.commit({ dir, message, author, committer }); + + expect(isomorphic.commit).toHaveBeenCalledWith({ + fs, + dir, + message, + author, + committer, + }); + }); + }); + + describe('clone', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const url = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + + await git.clone({ url, dir }); + + expect(isomorphic.clone).toHaveBeenCalledWith({ + fs, + http, + url, + dir, + singleBranch: true, + depth: 1, + onProgress: expect.any(Function), + headers: { + 'user-agent': 'git/@isomorphic-git', + }, + onAuth: expect.any(Function), + }); + }); + + it('should call isomorphic-git with the correct arguments (Bearer)', async () => { + const url = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + token: 'test', + }; + const git = Git.fromAuth(auth); + + await git.clone({ url, dir }); + + expect(isomorphic.clone).toHaveBeenCalledWith({ + fs, + http, + url, + dir, + singleBranch: true, + depth: 1, + onProgress: expect.any(Function), + headers: { + Authorization: 'Bearer test', + 'user-agent': 'git/@isomorphic-git', + }, + onAuth: expect.any(Function), + }); + }); + + it('should pass a function that returns the authorization as the onAuth handler when username and password are specified', async () => { + const url = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + + await git.clone({ url, dir }); + + const { onAuth } = ( + isomorphic.clone as unknown as jest.Mock<(typeof isomorphic)['clone']> + ).mock.calls[0][0]!; + + expect(onAuth()).toEqual(auth); + }); + + it('should pass the provided callback as the onAuth handler when on auth is specified', async () => { + const url = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + username: 'from', + password: 'callback', + }; + + const git = Git.fromAuth({ onAuth: () => auth }); + + await git.clone({ url, dir }); + + const { onAuth } = ( + isomorphic.clone as unknown as jest.Mock<(typeof isomorphic)['clone']> + ).mock.calls[0][0]!; + + expect(onAuth()).toEqual(auth); + }); + + it('should propagate the data from the error handler', async () => { + const url = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + + (isomorphic.clone as jest.Mock).mockImplementation(() => { + const error: Error & { data?: unknown } = new Error('mock error'); + error.data = { some: 'more information here' }; + + throw error; + }); + + await expect(git.clone({ url, dir })).rejects.toThrow( + 'more information here', + ); + }); + }); + + describe('currentBranch', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const dir = '/some/mock/dir'; + const fullName = true; + const git = Git.fromAuth({}); + + await git.currentBranch({ dir, fullName }); + + expect(isomorphic.currentBranch).toHaveBeenCalledWith({ + fs, + dir, + fullname: true, + }); + + await git.currentBranch({ dir }); + + expect(isomorphic.currentBranch).toHaveBeenCalledWith({ + fs, + dir, + fullname: false, + }); + }); + }); + + describe('fetch', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const remote = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + + await git.fetch({ remote, dir, tags: true }); + + expect(isomorphic.fetch).toHaveBeenCalledWith({ + fs, + http, + remote, + dir, + tags: true, + onProgress: expect.any(Function), + headers: { + 'user-agent': 'git/@isomorphic-git', + }, + onAuth: expect.any(Function), + }); + }); + + it('should call isomorphic-git with the correct arguments (Bearer)', async () => { + const remote = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + token: 'test', + }; + const git = Git.fromAuth(auth); + + await git.fetch({ remote, dir }); + + expect(isomorphic.fetch).toHaveBeenCalledWith({ + fs, + http, + remote, + dir, + tags: false, + onProgress: expect.any(Function), + headers: { + Authorization: 'Bearer test', + 'user-agent': 'git/@isomorphic-git', + }, + onAuth: expect.any(Function), + }); + }); + + it('should pass a function that returns the authorization as the onAuth handler', async () => { + const remote = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + + await git.fetch({ remote, dir }); + + const { onAuth } = ( + isomorphic.fetch as unknown as jest.Mock<(typeof isomorphic)['fetch']> + ).mock.calls[0][0]!; + + expect(onAuth()).toEqual(auth); + }); + + it('should propagate the data from the error handler', async () => { + const remote = 'http://github.com/some/repo'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + + (isomorphic.fetch as jest.Mock).mockImplementation(() => { + const error: Error & { data?: unknown } = new Error('mock error'); + error.data = { some: 'more information here' }; + + throw error; + }); + + await expect(git.fetch({ remote, dir })).rejects.toThrow( + 'more information here', + ); + }); + }); + + describe('init', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const dir = '/some/mock/dir'; + const defaultBranch = 'master'; + + const git = Git.fromAuth({}); + + await git.init({ dir, defaultBranch }); + + expect(isomorphic.init).toHaveBeenCalledWith({ + fs, + dir, + defaultBranch, + }); + }); + }); + + describe('merge', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const dir = '/some/mock/dir'; + const author = { + name: 'author', + email: 'test@backstage.io', + }; + const committer = { + name: 'comitter', + email: 'test@backstage.io', + }; + const theirs = 'master'; + const ours = 'production'; + + const git = Git.fromAuth({}); + + await git.merge({ dir, theirs, ours, author, committer }); + + expect(isomorphic.merge).toHaveBeenCalledWith({ + fs, + dir, + ours, + theirs, + author, + committer, + }); + }); + }); + + describe('push', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const remote = 'origin'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + const remoteRef = 'master'; + const force = true; + + await git.push({ dir, remote, remoteRef, force }); + + expect(isomorphic.push).toHaveBeenCalledWith({ + fs, + http, + remote, + dir, + remoteRef, + force, + onProgress: expect.any(Function), + headers: { + 'user-agent': 'git/@isomorphic-git', + }, + onAuth: expect.any(Function), + }); + }); + + it('should call isomorphic-git with the correct arguments (Bearer)', async () => { + const remote = 'origin'; + const dir = '/some/mock/dir'; + const auth = { + token: 'test', + }; + const git = Git.fromAuth(auth); + const remoteRef = 'master'; + const force = true; + + await git.push({ dir, remote, remoteRef, force }); + + expect(isomorphic.push).toHaveBeenCalledWith({ + fs, + http, + remote, + dir, + remoteRef, + force, + onProgress: expect.any(Function), + headers: { + Authorization: 'Bearer test', + 'user-agent': 'git/@isomorphic-git', + }, + onAuth: expect.any(Function), + }); + }); + + it('should call isomorphic-git with remoteRef parameter', async () => { + const remote = 'origin'; + const remoteRef = 'refs/for/master'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + + await git.push({ dir, remote, remoteRef }); + + expect(isomorphic.push).toHaveBeenCalledWith({ + fs, + http, + remote, + remoteRef, + dir, + onProgress: expect.any(Function), + headers: { + 'user-agent': 'git/@isomorphic-git', + }, + onAuth: expect.any(Function), + }); + }); + + it('should pass a function that returns the authorization as the onAuth handler', async () => { + const remote = 'origin'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + const remoteRef = 'master'; + const force = true; + + await git.push({ remote, dir, remoteRef, force }); + + const { onAuth } = ( + isomorphic.push as unknown as jest.Mock<(typeof isomorphic)['push']> + ).mock.calls[0][0]!; + + expect(onAuth()).toEqual(auth); + }); + + it('should propagate the data from the error handler', async () => { + const remote = 'origin'; + const dir = '/some/mock/dir'; + const auth = { + username: 'blob', + password: 'hunter2', + }; + const git = Git.fromAuth(auth); + const remoteRef = 'master'; + const force = true; + + (isomorphic.push as jest.Mock).mockImplementation(() => { + const error: Error & { data?: unknown } = new Error('mock error'); + error.data = { some: 'more information here' }; + + throw error; + }); + + await expect(git.push({ remote, dir, remoteRef, force })).rejects.toThrow( + 'more information here', + ); + }); + }); + + describe('readCommit', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const dir = '/some/mock/dir'; + const sha = 'as43bd7'; + + const git = Git.fromAuth({}); + + await git.readCommit({ dir, sha }); + + expect(isomorphic.readCommit).toHaveBeenCalledWith({ + fs, + dir, + oid: sha, + }); + }); + }); + + describe('resolveRef', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const dir = '/some/mock/dir'; + const ref = 'as43bd7'; + + const git = Git.fromAuth({}); + + await git.resolveRef({ dir, ref }); + + expect(isomorphic.resolveRef).toHaveBeenCalledWith({ + fs, + dir, + ref, + }); + }); + }); + + describe('log', () => { + it('should call isomorphic-git with the correct arguments', async () => { + const dir = '/some/mock/dir'; + const ref = 'as43bd7'; + + const git = Git.fromAuth({}); + + await git.log({ dir, ref }); + + expect(isomorphic.log).toHaveBeenCalledWith({ + fs, + dir, + ref, + }); + }); + }); +}); diff --git a/packages/backend-common/src/scm/git.ts b/plugins/scaffolder-node/src/scm/git.ts similarity index 100% rename from packages/backend-common/src/scm/git.ts rename to plugins/scaffolder-node/src/scm/git.ts diff --git a/plugins/scaffolder-node/src/scm/index.ts b/plugins/scaffolder-node/src/scm/index.ts new file mode 100644 index 0000000000..f9c59e99eb --- /dev/null +++ b/plugins/scaffolder-node/src/scm/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { Git } from './git'; +export type { StaticAuthOptions, AuthCallbackOptions } from './git'; diff --git a/yarn.lock b/yarn.lock index 9c9a730679..44dd1b76ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6909,6 +6909,7 @@ __metadata: "@backstage/types": "workspace:^" fs-extra: ^11.2.0 globby: ^11.0.0 + isomorphic-git: ^1.23.0 jsonschema: ^1.2.6 p-limit: ^3.1.0 winston: ^3.2.1 From f633efa1c74365a4e7bb7cabfa48306f7d19e9ea Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 13 May 2024 10:35:20 +0200 Subject: [PATCH 363/567] refactor: apply review suggestions Signed-off-by: Camila Belo --- .changeset/famous-crabs-laugh.md | 1 - .changeset/strange-rocks-study.md | 5 +++++ packages/backend-common/src/reading/GerritUrlReader.ts | 8 +------- packages/backend-common/src/reading/git.ts | 7 ++++++- 4 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 .changeset/strange-rocks-study.md diff --git a/.changeset/famous-crabs-laugh.md b/.changeset/famous-crabs-laugh.md index 6427fbbc82..282cbb9f93 100644 --- a/.changeset/famous-crabs-laugh.md +++ b/.changeset/famous-crabs-laugh.md @@ -1,6 +1,5 @@ --- '@backstage/backend-common': patch -'@backstage/plugin-scaffolder-node': patch --- Preparing for a stable new backend system release, we are deprecating utilities in the `backend-common` that are not used by the core framework, such as the isomorphic `Git` class. As we will no longer support the isomorphic `Git` utility in the framework packages, we recommend plugins that start maintaining their own implementation of this class. diff --git a/.changeset/strange-rocks-study.md b/.changeset/strange-rocks-study.md new file mode 100644 index 0000000000..75ceb745fb --- /dev/null +++ b/.changeset/strange-rocks-study.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-node': patch +--- + +To remove the dependency on the soon-to-be-deprecated `backend-common` package, this package now maintains its own isomorphic Git class implementation. diff --git a/packages/backend-common/src/reading/GerritUrlReader.ts b/packages/backend-common/src/reading/GerritUrlReader.ts index b8a3d98392..52a541382b 100644 --- a/packages/backend-common/src/reading/GerritUrlReader.ts +++ b/packages/backend-common/src/reading/GerritUrlReader.ts @@ -45,13 +45,7 @@ import { SearchResponse, UrlReader, } from './types'; -import { StaticAuthOptions, AuthCallbackOptions, Git } from './git'; - -export function isAuthCallbackOptions( - options: StaticAuthOptions | AuthCallbackOptions, -): options is AuthCallbackOptions { - return 'onAuth' in options; -} +import { Git } from './git'; const pipeline = promisify(pipelineCb); diff --git a/packages/backend-common/src/reading/git.ts b/packages/backend-common/src/reading/git.ts index ecd38d6c0c..119d4f64bd 100644 --- a/packages/backend-common/src/reading/git.ts +++ b/packages/backend-common/src/reading/git.ts @@ -18,7 +18,6 @@ import fs from 'fs-extra'; import isomorphicGit, { ProgressCallback, AuthCallback } from 'isomorphic-git'; import http from 'isomorphic-git/http/node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { isAuthCallbackOptions } from './GerritUrlReader'; /** * Configure static credential for authentication @@ -42,6 +41,12 @@ export type AuthCallbackOptions = { logger?: LoggerService; }; +function isAuthCallbackOptions( + options: StaticAuthOptions | AuthCallbackOptions, +): options is AuthCallbackOptions { + return 'onAuth' in options; +} + /* provider username password Azure 'notempty' token From 8472777e25872ba41e26568173e885af31f37539 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 13 May 2024 11:36:24 +0200 Subject: [PATCH 364/567] Update docs/auth/github/provider.md Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: Vincenzo Scamporlino --- docs/auth/github/provider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/auth/github/provider.md b/docs/auth/github/provider.md index 4accc7841b..1604b607ff 100644 --- a/docs/auth/github/provider.md +++ b/docs/auth/github/provider.md @@ -48,7 +48,7 @@ auth: - resolver: usernameMatchingUserEntityName ``` -The GitHub provider is a structure with five configuration keys: +The GitHub provider is a structure with these configuration keys: - `clientId`: The client ID that you generated on GitHub, e.g. `b59241722e3c3b4816e2` From 32e329e23f24c9472ec9fca6b0ac4fc6ff173f5e Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 13 May 2024 11:41:23 +0200 Subject: [PATCH 365/567] backend-app-api: add missing test Signed-off-by: Vincenzo Scamporlino --- packages/backend-app-api/src/http/readHelmetOptions.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/backend-app-api/src/http/readHelmetOptions.test.ts b/packages/backend-app-api/src/http/readHelmetOptions.test.ts index 619a4e6a17..97daef359a 100644 --- a/packages/backend-app-api/src/http/readHelmetOptions.test.ts +++ b/packages/backend-app-api/src/http/readHelmetOptions.test.ts @@ -48,6 +48,7 @@ describe('readHelmetOptions', () => { key: ['value'], 'img-src': false, scriptSrcAttr: ['custom'], + 'object-src': ['asd'], }, }); expect(readHelmetOptions(config)).toEqual({ @@ -58,7 +59,7 @@ describe('readHelmetOptions', () => { 'base-uri': ["'self'"], 'font-src': ["'self'", 'https:', 'data:'], 'frame-ancestors': ["'self'"], - 'object-src': ["'none'"], + 'object-src': ['asd'], 'script-src': ["'self'", "'unsafe-eval'"], 'style-src': ["'self'", 'https:', "'unsafe-inline'"], 'script-src-attr': ['custom'], From 2d707aa3598c51cd187076c21ab3594218a54848 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 13 May 2024 12:20:52 +0200 Subject: [PATCH 366/567] changesets: exit pre release Signed-off-by: Vincenzo Scamporlino --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 4a813e4e19..8c72a9d481 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "next", "initialVersions": { "example-app": "0.2.96", From e5e1af521aefa977d255978428817e1e9bea4fcc Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Mon, 13 May 2024 15:53:17 +0530 Subject: [PATCH 367/567] updated document of github, gitlab location Signed-off-by: JeevaRamanathan --- docs/integrations/github/locations.md | 8 +++++--- docs/integrations/gitlab/locations.md | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/integrations/github/locations.md b/docs/integrations/github/locations.md index 7d3ce632d6..ae83b38e1e 100644 --- a/docs/integrations/github/locations.md +++ b/docs/integrations/github/locations.md @@ -28,9 +28,11 @@ integrations: token: ${GHE_TOKEN} ``` -> Note: A public GitHub provider is added automatically at startup for -> convenience, so you only need to list it if you want to supply a -> [token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). +:::note Note + +A public GitHub provider is added automatically at startup for convenience, so you only need to list it if you want to supply a [token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). + +::: Directly under the `github` key is a list of provider configurations, where you can list the various GitHub-compatible providers you want to be able to fetch diff --git a/docs/integrations/gitlab/locations.md b/docs/integrations/gitlab/locations.md index a71fc9bcba..f5995a3ac1 100644 --- a/docs/integrations/gitlab/locations.md +++ b/docs/integrations/gitlab/locations.md @@ -19,9 +19,11 @@ integrations: token: ${GITLAB_TOKEN} ``` -> Note: A public GitLab provider is added automatically at startup for -> convenience, so you only need to list it if you want to supply a -> [token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html). +:::note Note + +A public GitLab provider is added automatically at startup for convenience, so you only need to list it if you want to supply a [token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html). + +::: Directly under the `gitlab` key is a list of provider configurations, where you can list the GitLab providers you want to fetch data from. Each entry is a From 207f9624612b2b4f59dd47f9f7f483ec865de5a4 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 13 May 2024 12:45:28 +0200 Subject: [PATCH 368/567] catalog-github: defaultNamespace to alwaysUseDefaultNamespace Signed-off-by: Vincenzo Scamporlino --- .changeset/wild-cats-hug.md | 2 +- plugins/catalog-backend-module-github-org/src/module.ts | 2 +- plugins/catalog-backend-module-github/api-report.md | 4 ++-- .../src/providers/GithubMultiOrgEntityProvider.test.ts | 4 ++-- .../src/providers/GithubMultiOrgEntityProvider.ts | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/wild-cats-hug.md b/.changeset/wild-cats-hug.md index 180bf50747..e33d399dab 100644 --- a/.changeset/wild-cats-hug.md +++ b/.changeset/wild-cats-hug.md @@ -2,6 +2,6 @@ '@backstage/plugin-catalog-backend-module-github': patch --- -Added `defaultNamespace` option to `GithubMultiOrgEntityProvider`. +Added `alwaysUseDefaultNamespace` option to `GithubMultiOrgEntityProvider`. If set to true, the provider will use `default` as the namespace for all group entities. Groups with the same name across different orgs will be considered the same group. diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index 517968c4f0..afcf2df976 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -120,7 +120,7 @@ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ logger, userTransformer, teamTransformer, - defaultNamespace: + alwaysUseDefaultNamespace: definitions.length === 1 && definition.orgs?.length === 1, }), ); diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 3c7b563593..0b68170255 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -150,7 +150,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { orgs?: string[]; userTransformer?: UserTransformer; teamTransformer?: TeamTransformer; - defaultNamespace?: boolean; + alwaysUseDefaultNamespace?: boolean; }); // (undocumented) connect(connection: EntityProviderConnection): Promise; @@ -166,7 +166,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { // @public export interface GithubMultiOrgEntityProviderOptions { - defaultNamespace?: boolean; + alwaysUseDefaultNamespace?: boolean; events?: EventsService; githubCredentialsProvider?: GithubCredentialsProvider; githubUrl: string; diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index 87d197d4ea..c89901fcb3 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -647,7 +647,7 @@ describe('GithubMultiOrgEntityProvider', () => { }); }); - it('should use the default namespace if options.defaultNamespace is provided', async () => { + it('should use the default namespace if options.alwaysUseDefaultNamespace is provided', async () => { mockClient .mockResolvedValueOnce({ organization: { @@ -764,7 +764,7 @@ describe('GithubMultiOrgEntityProvider', () => { githubUrl: 'https://github.com', logger, orgs: ['orgA', 'orgB'], - defaultNamespace: true, + alwaysUseDefaultNamespace: true, }); await entityProvider.connect(entityProviderConnection); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index bac6383459..756b9902c6 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -150,7 +150,7 @@ export interface GithubMultiOrgEntityProviderOptions { * * If set to true, groups with the same name across different orgs will be considered the same group. */ - defaultNamespace?: boolean; + alwaysUseDefaultNamespace?: boolean; /** * Optionally include a user transformer for transforming from GitHub users to User Entities @@ -207,7 +207,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { userTransformer: options.userTransformer, teamTransformer: options.teamTransformer, events: options.events, - defaultNamespace: options.defaultNamespace, + alwaysUseDefaultNamespace: options.alwaysUseDefaultNamespace, }); provider.schedule(options.schedule); @@ -226,7 +226,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { orgs?: string[]; userTransformer?: UserTransformer; teamTransformer?: TeamTransformer; - defaultNamespace?: boolean; + alwaysUseDefaultNamespace?: boolean; }, ) {} @@ -857,7 +857,7 @@ export class GithubMultiOrgEntityProvider implements EntityProvider { const result = await defaultOrganizationTeamTransformer(team, ctx); if (result && result.spec) { - if (!this.options.defaultNamespace) { + if (!this.options.alwaysUseDefaultNamespace) { result.metadata.namespace = ctx.org.toLocaleLowerCase('en-US'); } From 84d443db5f958dba6dfaa3e32dd59d31150daa53 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 13 May 2024 12:54:25 +0200 Subject: [PATCH 369/567] catalog-github: use logger Signed-off-by: Vincenzo Scamporlino --- .../src/GithubOrgEntityCleanerProvider.tsx | 8 ++++++-- plugins/catalog-backend-module-github-org/src/module.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx b/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx index 386fd367c2..a00cd504d7 100644 --- a/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx +++ b/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx @@ -13,13 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { EntityProvider, EntityProviderConnection, } from '@backstage/plugin-catalog-node'; export class GithubOrgEntityCleanerProvider implements EntityProvider { - constructor(private readonly options: { id: string }) {} + logger: LoggerService; + constructor(private readonly options: { id: string; logger: LoggerService }) { + this.logger = options.logger.child({ target: this.getProviderName() }); + } getProviderName() { return `GithubOrgEntityProvider:${this.options.id}`; @@ -32,7 +36,7 @@ export class GithubOrgEntityCleanerProvider implements EntityProvider { entities: [], }) .catch(error => { - console.error('Failed to clean up entities', error); + this.logger.error('Failed to clean up entities', error); }); } } diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index afcf2df976..31d4cef1c0 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -106,7 +106,7 @@ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ for (const definition of definitions) { catalog.addEntityProvider( - new GithubOrgEntityCleanerProvider({ id: definition.id }), + new GithubOrgEntityCleanerProvider({ id: definition.id, logger }), ); catalog.addEntityProvider( GithubMultiOrgEntityProvider.fromConfig(config, { From 97df97d3317921d85faecb2c3724eb2513ac3d46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 11:06:59 +0000 Subject: [PATCH 370/567] chore(deps): update ossf/scorecard-action action to v2.3.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0b883dd75a..a51e3fefe8 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -39,7 +39,7 @@ jobs: persist-credentials: false - name: 'Run analysis' - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif From a394b66c3eaa30c2901b0ff0911950aacc2cc0d0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 29 Apr 2024 09:32:02 +0200 Subject: [PATCH 371/567] refator(backend-common): extract test utilities to plugin api Signed-off-by: Camila Belo --- .../backend-common/api-report-testUtils.md | 27 +++---- packages/backend-common/src/deprecated.ts | 43 ++++++++++ packages/backend-common/src/testUtils.ts | 70 ++-------------- .../api-report-testUtils.md | 26 ++++++ packages/backend-plugin-api/package.json | 4 + packages/backend-plugin-api/src/testUtils.ts | 81 +++++++++++++++++++ .../app-backend/src/service/appPlugin.test.ts | 2 +- .../src/stages/publish/local.test.ts | 2 +- 8 files changed, 173 insertions(+), 82 deletions(-) create mode 100644 packages/backend-common/src/deprecated.ts create mode 100644 packages/backend-plugin-api/api-report-testUtils.md create mode 100644 packages/backend-plugin-api/src/testUtils.ts diff --git a/packages/backend-common/api-report-testUtils.md b/packages/backend-common/api-report-testUtils.md index 9dbdade10e..d53748234e 100644 --- a/packages/backend-common/api-report-testUtils.md +++ b/packages/backend-common/api-report-testUtils.md @@ -3,24 +3,19 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -// @public -export function overridePackagePathResolution( - options: OverridePackagePathResolutionOptions, -): PackagePathResolutionOverride; +import { overridePackagePathResolution as overridePackagePathResolution_2 } from '@backstage/backend-plugin-api/testUtils'; +import { OverridePackagePathResolutionOptions as OverridePackagePathResolutionOptions_2 } from '@backstage/backend-plugin-api/testUtils'; +import { PackagePathResolutionOverride as PackagePathResolutionOverride_2 } from '@backstage/backend-plugin-api/testUtils'; -// @public (undocumented) -export interface OverridePackagePathResolutionOptions { - packageName: string; - path?: string; - paths?: { - [path in string]: string | (() => string); - }; -} +// @public @deprecated (undocumented) +export const overridePackagePathResolution: typeof overridePackagePathResolution_2; -// @public (undocumented) -export interface PackagePathResolutionOverride { - restore(): void; -} +// @public @deprecated (undocumented) +export type OverridePackagePathResolutionOptions = + OverridePackagePathResolutionOptions_2; + +// @public @deprecated (undocumented) +export type PackagePathResolutionOverride = PackagePathResolutionOverride_2; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-common/src/deprecated.ts b/packages/backend-common/src/deprecated.ts new file mode 100644 index 0000000000..397d97f20e --- /dev/null +++ b/packages/backend-common/src/deprecated.ts @@ -0,0 +1,43 @@ +/* + * 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 { + overridePackagePathResolution as _overridePackagePathResolution, + OverridePackagePathResolutionOptions as _OverridePackagePathResolutionOptions, + PackagePathResolutionOverride as _PackagePathResolutionOverride, +} from '@backstage/backend-plugin-api/testUtils'; + +/** + * @public + * @deprecated This function is deprecated and will be removed in future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `overridePackagePathResolution` function from the `@backstage/backend-plugin-api/testUtils` package instead. + */ +export const overridePackagePathResolution = _overridePackagePathResolution; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `OverridePackagePathResolutionOptions` type from the `@backstage/backend-plugin-api/testUtils` package instead. + */ +export type OverridePackagePathResolutionOptions = + _OverridePackagePathResolutionOptions; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `PackagePathResolutionOverride` type from the `@backstage/backend-plugin-api/testUtils` package instead. + */ +export type PackagePathResolutionOverride = _PackagePathResolutionOverride; diff --git a/packages/backend-common/src/testUtils.ts b/packages/backend-common/src/testUtils.ts index 9616ab1701..bae633428f 100644 --- a/packages/backend-common/src/testUtils.ts +++ b/packages/backend-common/src/testUtils.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * 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. @@ -14,66 +14,8 @@ * limitations under the License. */ -import { packagePathMocks } from './paths'; -import { posix as posixPath, resolve as resolvePath } from 'path'; - -/** @public */ -export interface PackagePathResolutionOverride { - /** Restores the normal behavior of resolvePackagePath */ - restore(): void; -} - -/** @public */ -export interface OverridePackagePathResolutionOptions { - /** The name of the package to mock the resolved path of */ - packageName: string; - - /** A replacement for the root package path */ - path?: string; - - /** - * Replacements for package sub-paths, each key must be an exact match of the posix-style path - * that is being resolved within the package. - * - * For example, code calling `resolvePackagePath('x', 'foo', 'bar')` would match only the following - * configuration: `overridePackagePathResolution({ packageName: 'x', paths: { 'foo/bar': baz } })` - */ - paths?: { [path in string]: string | (() => string) }; -} - -/** - * This utility helps you override the paths returned by `resolvePackagePath` for a given package. - * - * @public - */ -export function overridePackagePathResolution( - options: OverridePackagePathResolutionOptions, -): PackagePathResolutionOverride { - const name = options.packageName; - - if (packagePathMocks.has(name)) { - throw new Error( - `Tried to override resolution for '${name}' more than once for package '${name}'`, - ); - } - - packagePathMocks.set(name, paths => { - const joinedPath = posixPath.join(...paths); - const localResolver = options.paths?.[joinedPath]; - if (localResolver) { - return typeof localResolver === 'function' - ? localResolver() - : localResolver; - } - if (options.path) { - return resolvePath(options.path, ...paths); - } - return undefined; - }); - - return { - restore() { - packagePathMocks.delete(name); - }, - }; -} +export { + overridePackagePathResolution, + type OverridePackagePathResolutionOptions, + type PackagePathResolutionOverride, +} from './deprecated'; diff --git a/packages/backend-plugin-api/api-report-testUtils.md b/packages/backend-plugin-api/api-report-testUtils.md new file mode 100644 index 0000000000..8c9bba8398 --- /dev/null +++ b/packages/backend-plugin-api/api-report-testUtils.md @@ -0,0 +1,26 @@ +## API Report File for "@backstage/backend-plugin-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public +export function overridePackagePathResolution( + options: OverridePackagePathResolutionOptions, +): PackagePathResolutionOverride; + +// @public (undocumented) +export interface OverridePackagePathResolutionOptions { + packageName: string; + path?: string; + paths?: { + [path in string]: string | (() => string); + }; +} + +// @public (undocumented) +export interface PackagePathResolutionOverride { + restore(): void; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index fff99c0370..c83ef854b8 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -21,6 +21,7 @@ "exports": { ".": "./src/index.ts", "./alpha": "./src/alpha.ts", + "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -30,6 +31,9 @@ "alpha": [ "src/alpha.ts" ], + "testUtils": [ + "src/testUtils.ts" + ], "package.json": [ "package.json" ] diff --git a/packages/backend-plugin-api/src/testUtils.ts b/packages/backend-plugin-api/src/testUtils.ts new file mode 100644 index 0000000000..a04cae4c25 --- /dev/null +++ b/packages/backend-plugin-api/src/testUtils.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: Remove this relative import when extrating the path utilities to this package +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { packagePathMocks } from '../../backend-common/src/paths'; +import { posix as posixPath, resolve as resolvePath } from 'path'; + +/** @public */ +export interface PackagePathResolutionOverride { + /** Restores the normal behavior of resolvePackagePath */ + restore(): void; +} + +/** @public */ +export interface OverridePackagePathResolutionOptions { + /** The name of the package to mock the resolved path of */ + packageName: string; + + /** A replacement for the root package path */ + path?: string; + + /** + * Replacements for package sub-paths, each key must be an exact match of the posix-style path + * that is being resolved within the package. + * + * For example, code calling `resolvePackagePath('x', 'foo', 'bar')` would match only the following + * configuration: `overridePackagePathResolution({ packageName: 'x', paths: { 'foo/bar': baz } })` + */ + paths?: { [path in string]: string | (() => string) }; +} + +/** + * This utility helps you override the paths returned by `resolvePackagePath` for a given package. + * + * @public + */ +export function overridePackagePathResolution( + options: OverridePackagePathResolutionOptions, +): PackagePathResolutionOverride { + const name = options.packageName; + + if (packagePathMocks.has(name)) { + throw new Error( + `Tried to override resolution for '${name}' more than once for package '${name}'`, + ); + } + + packagePathMocks.set(name, paths => { + const joinedPath = posixPath.join(...paths); + const localResolver = options.paths?.[joinedPath]; + if (localResolver) { + return typeof localResolver === 'function' + ? localResolver() + : localResolver; + } + if (options.path) { + return resolvePath(options.path, ...paths); + } + return undefined; + }); + + return { + restore() { + packagePathMocks.delete(name); + }, + }; +} diff --git a/plugins/app-backend/src/service/appPlugin.test.ts b/plugins/app-backend/src/service/appPlugin.test.ts index 647697c204..c239d476bd 100644 --- a/plugins/app-backend/src/service/appPlugin.test.ts +++ b/plugins/app-backend/src/service/appPlugin.test.ts @@ -22,7 +22,7 @@ import { } from '@backstage/backend-test-utils'; import { appPlugin } from './appPlugin'; import { createRootLogger } from '@backstage/backend-common'; -import { overridePackagePathResolution } from '@backstage/backend-common/testUtils'; +import { overridePackagePathResolution } from '@backstage/backend-plugin-api/testUtils'; const mockDir = createMockDirectory(); overridePackagePathResolution({ diff --git a/plugins/techdocs-node/src/stages/publish/local.test.ts b/plugins/techdocs-node/src/stages/publish/local.test.ts index 4019f80d11..89a38c7ed2 100644 --- a/plugins/techdocs-node/src/stages/publish/local.test.ts +++ b/plugins/techdocs-node/src/stages/publish/local.test.ts @@ -17,7 +17,7 @@ import { getVoidLogger, PluginEndpointDiscovery, } from '@backstage/backend-common'; -import { overridePackagePathResolution } from '@backstage/backend-common/testUtils'; +import { overridePackagePathResolution } from '@backstage/backend-plugin-api/testUtils'; import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; From d229dc49adbb3059153e14cf312a4889c8a4c66e Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 30 Apr 2024 14:03:56 +0200 Subject: [PATCH 372/567] refator(backend-common): extract path utilities to plugin api Signed-off-by: Camila Belo --- .changeset/funny-cars-jump.md | 24 ++++++++++ .../implementations/auth/DatabaseKeyStore.ts | 7 ++- packages/backend-common/src/deprecated.ts | 27 ++++++++++++ packages/backend-common/src/index.ts | 6 ++- .../src/reading/tree/ZipArchiveResponse.ts | 2 +- .../src/schemas/appBackendModule.ts | 2 +- packages/backend-plugin-api/package.json | 3 ++ packages/backend-plugin-api/src/index.ts | 1 + .../src/paths.test.ts | 0 .../src/paths.ts | 0 packages/backend-plugin-api/src/testUtils.ts | 2 +- .../src/database/migrateBackendTasks.ts | 2 +- .../src/filesystem/MockDirectory.ts | 2 +- packages/repo-tools/package.json | 44 +++++++++---------- .../package/schema/openapi/generate/client.ts | 2 +- .../src/lib/assets/StaticAssetsStore.ts | 10 ++--- .../src/lib/assets/findStaticAssets.ts | 2 +- plugins/app-backend/src/service/router.ts | 2 +- .../auth-backend/src/database/AuthDatabase.ts | 2 +- .../src/database/migrations.ts | 2 +- .../src/database/migrations.ts | 2 +- .../database/DatabaseNotificationsStore.ts | 6 +-- .../src/actions/fetch/cookiecutter.ts | 7 +-- .../src/actions/githubPullRequest.ts | 6 ++- .../src/actions/gitlabMergeRequest.ts | 2 +- .../src/actions/gitlabRepoPush.ts | 2 +- .../src/lib/templating/SecureTemplater.ts | 2 +- .../actions/builtin/catalog/write.ts | 2 +- .../scaffolder/actions/builtin/fetch/plain.ts | 3 +- .../actions/builtin/fetch/plainFile.ts | 3 +- .../builtin/fetch/template.examples.test.ts | 3 +- .../actions/builtin/fetch/template.test.ts | 3 +- .../actions/builtin/fetch/template.ts | 3 +- .../actions/builtin/filesystem/delete.ts | 2 +- .../actions/builtin/filesystem/rename.ts | 2 +- .../src/scaffolder/dryrun/createDryRunner.ts | 6 ++- .../src/scaffolder/tasks/DatabaseTaskStore.ts | 6 +-- plugins/scaffolder-node/src/actions/fetch.ts | 3 +- plugins/scaffolder-node/src/actions/util.ts | 2 +- .../src/files/deserializeDirectoryContents.ts | 2 +- .../src/files/serializeDirectoryContents.ts | 2 +- .../src/database/DatabaseDocumentStore.ts | 6 +-- plugins/techdocs-node/src/helpers.ts | 3 +- .../src/stages/generate/helpers.ts | 2 +- .../techdocs-node/src/stages/publish/local.ts | 4 +- .../src/database/DatabaseUserSettingsStore.ts | 6 +-- yarn.lock | 5 ++- 47 files changed, 151 insertions(+), 86 deletions(-) create mode 100644 .changeset/funny-cars-jump.md rename packages/{backend-common => backend-plugin-api}/src/paths.test.ts (100%) rename packages/{backend-common => backend-plugin-api}/src/paths.ts (100%) diff --git a/.changeset/funny-cars-jump.md b/.changeset/funny-cars-jump.md new file mode 100644 index 0000000000..cbaf01c7d0 --- /dev/null +++ b/.changeset/funny-cars-jump.md @@ -0,0 +1,24 @@ +--- +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/backend-dynamic-feature-service': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-user-settings-backend': patch +'@backstage/backend-plugin-api': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-app-api': patch +'@backstage/backend-common': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/backend-tasks': patch +'@backstage/plugin-techdocs-node': patch +'@backstage/plugin-auth-backend': patch +'@backstage/repo-tools': patch +'@backstage/plugin-app-backend': patch +--- + +Extract path utilities from `backend-common` to the `backend-plugin-api` package. diff --git a/packages/backend-app-api/src/services/implementations/auth/DatabaseKeyStore.ts b/packages/backend-app-api/src/services/implementations/auth/DatabaseKeyStore.ts index 6a1536fd18..5bf9244707 100644 --- a/packages/backend-app-api/src/services/implementations/auth/DatabaseKeyStore.ts +++ b/packages/backend-app-api/src/services/implementations/auth/DatabaseKeyStore.ts @@ -14,11 +14,14 @@ * limitations under the License. */ -import { DatabaseService, LoggerService } from '@backstage/backend-plugin-api'; +import { + DatabaseService, + LoggerService, + resolvePackagePath, +} from '@backstage/backend-plugin-api'; import { DateTime } from 'luxon'; import { Knex } from 'knex'; import { JsonObject } from '@backstage/types'; -import { resolvePackagePath } from '@backstage/backend-common'; import { KeyStore } from './types'; const MIGRATIONS_TABLE = 'backstage_backend_public_keys__knex_migrations'; diff --git a/packages/backend-common/src/deprecated.ts b/packages/backend-common/src/deprecated.ts index 397d97f20e..cee15aadb6 100644 --- a/packages/backend-common/src/deprecated.ts +++ b/packages/backend-common/src/deprecated.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +import { + resolvePackagePath as _resolvePackagePath, + resolveSafeChildPath as _resolveSafeChildPath, + isChildPath as _isChildPath, +} from '@backstage/backend-plugin-api'; + import { overridePackagePathResolution as _overridePackagePathResolution, OverridePackagePathResolutionOptions as _OverridePackagePathResolutionOptions, @@ -41,3 +47,24 @@ export type OverridePackagePathResolutionOptions = * Please use the `PackagePathResolutionOverride` type from the `@backstage/backend-plugin-api/testUtils` package instead. */ export type PackagePathResolutionOverride = _PackagePathResolutionOverride; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `resolvePackagePath` function from the `@backstage/backend-plugin-api` package instead. + */ +export const resolvePackagePath = _resolvePackagePath; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see + * Please use the `resolveSafeChildPath` function from the `@backstage/backend-plugin-api` package instead. + */ +export const resolveSafeChildPath = _resolveSafeChildPath; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see + * Please use the `isChildPath` function from the `@backstage/cli-common` package instead. + */ +export const isChildPath = _isChildPath; diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 4b1314b7d2..1d01410408 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -25,12 +25,16 @@ export type { LegacyCreateRouter } from './legacy'; export * from './auth'; export * from './cache'; export { loadBackendConfig } from './config'; +export { + resolvePackagePath, + resolveSafeChildPath, + isChildPath, +} from './deprecated'; export * from './database'; export * from './discovery'; export * from './hot'; export * from './logging'; export * from './middleware'; -export * from './paths'; export * from './reading'; export * from './scm'; export * from './service'; diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index a191163517..412819c5b9 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -25,7 +25,7 @@ import { ReadTreeResponseFile, } from '../types'; import { streamToBuffer } from './util'; -import { resolveSafeChildPath } from '../../paths'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; /** * Wraps a zip archive stream into a tree response reader. diff --git a/packages/backend-dynamic-feature-service/src/schemas/appBackendModule.ts b/packages/backend-dynamic-feature-service/src/schemas/appBackendModule.ts index b44913edfd..6e54379e90 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/appBackendModule.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/appBackendModule.ts @@ -17,13 +17,13 @@ import { coreServices, createBackendModule, + resolvePackagePath, } from '@backstage/backend-plugin-api'; import { dynamicPluginsSchemasServiceRef } from './schemas'; import { configSchemaExtensionPoint, loadCompiledConfigSchema, } from '@backstage/plugin-app-node'; -import { resolvePackagePath } from '@backstage/backend-common'; /** @public */ export const dynamicPluginsFrontendSchemas = createBackendModule({ diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index c83ef854b8..ca954e4c56 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -53,7 +53,9 @@ }, "dependencies": { "@backstage/backend-tasks": "workspace:^", + "@backstage/cli-common": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/types": "workspace:^", @@ -62,6 +64,7 @@ "knex": "^3.0.0" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^" } } diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts index 8e0b23d20b..c2e2a13a89 100644 --- a/packages/backend-plugin-api/src/index.ts +++ b/packages/backend-plugin-api/src/index.ts @@ -22,4 +22,5 @@ export * from './services'; export type { BackendFeature } from './types'; +export * from './paths'; export * from './wiring'; diff --git a/packages/backend-common/src/paths.test.ts b/packages/backend-plugin-api/src/paths.test.ts similarity index 100% rename from packages/backend-common/src/paths.test.ts rename to packages/backend-plugin-api/src/paths.test.ts diff --git a/packages/backend-common/src/paths.ts b/packages/backend-plugin-api/src/paths.ts similarity index 100% rename from packages/backend-common/src/paths.ts rename to packages/backend-plugin-api/src/paths.ts diff --git a/packages/backend-plugin-api/src/testUtils.ts b/packages/backend-plugin-api/src/testUtils.ts index a04cae4c25..ba09145bf0 100644 --- a/packages/backend-plugin-api/src/testUtils.ts +++ b/packages/backend-plugin-api/src/testUtils.ts @@ -16,7 +16,7 @@ // TODO: Remove this relative import when extrating the path utilities to this package // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { packagePathMocks } from '../../backend-common/src/paths'; +import { packagePathMocks } from './paths'; import { posix as posixPath, resolve as resolvePath } from 'path'; /** @public */ diff --git a/packages/backend-tasks/src/database/migrateBackendTasks.ts b/packages/backend-tasks/src/database/migrateBackendTasks.ts index 9530668e3e..ae5048fbf7 100644 --- a/packages/backend-tasks/src/database/migrateBackendTasks.ts +++ b/packages/backend-tasks/src/database/migrateBackendTasks.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolvePackagePath } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; import { DB_MIGRATIONS_TABLE } from './tables'; diff --git a/packages/backend-test-utils/src/filesystem/MockDirectory.ts b/packages/backend-test-utils/src/filesystem/MockDirectory.ts index fff8a35a06..1c10c77fc6 100644 --- a/packages/backend-test-utils/src/filesystem/MockDirectory.ts +++ b/packages/backend-test-utils/src/filesystem/MockDirectory.ts @@ -15,7 +15,7 @@ */ import os from 'os'; -import { isChildPath } from '@backstage/backend-common'; +import { isChildPath } from '@backstage/backend-plugin-api'; import fs from 'fs-extra'; import textextensions from 'textextensions'; import { diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 14a2cbca1d..41492f2366 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -8,31 +8,42 @@ "backstage": { "role": "cli" }, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/repo-tools" }, - "keywords": [ - "backstage" - ], "license": "Apache-2.0", "main": "dist/index.cjs.js", - "scripts": { - "build": "backstage-cli package build", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test", - "clean": "backstage-cli package clean", - "start": "nodemon --" - }, "bin": { "backstage-repo-tools": "bin/backstage-repo-tools" }, + "files": [ + "bin", + "dist/**/*.js", + "templates", + "openapitools.json" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "start": "nodemon --", + "test": "backstage-cli package test" + }, + "nodemonConfig": { + "exec": "bin/backstage-repo-tools", + "ext": "ts", + "watch": "./src" + }, "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", "@apisyouwonthate/style-guide": "^1.4.0", - "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/cli-node": "workspace:^", @@ -84,16 +95,5 @@ "prettier": { "optional": true } - }, - "files": [ - "bin", - "dist/**/*.js", - "templates", - "openapitools.json" - ], - "nodemonConfig": { - "watch": "./src", - "exec": "bin/backstage-repo-tools", - "ext": "ts" } } diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 5dfed05f93..6963e02664 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -24,7 +24,7 @@ import { paths as cliPaths } from '../../../../../lib/paths'; import { mkdirpSync } from 'fs-extra'; import fs from 'fs-extra'; import { exec } from '../../../../../lib/exec'; -import { resolvePackagePath } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers'; async function generate(outputDirectory: string) { diff --git a/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts b/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts index 05d6424013..1179a11ef3 100644 --- a/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts +++ b/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import { - PluginDatabaseManager, - resolvePackagePath, -} from '@backstage/backend-common'; +import { PluginDatabaseManager } from '@backstage/backend-common'; import { Knex } from 'knex'; import { DateTime } from 'luxon'; import partition from 'lodash/partition'; import { StaticAsset, StaticAssetInput, StaticAssetProvider } from './types'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + resolvePackagePath, +} from '@backstage/backend-plugin-api'; const migrationsDir = resolvePackagePath( '@backstage/plugin-app-backend', diff --git a/plugins/app-backend/src/lib/assets/findStaticAssets.ts b/plugins/app-backend/src/lib/assets/findStaticAssets.ts index 7ecea7146e..c8731bfc93 100644 --- a/plugins/app-backend/src/lib/assets/findStaticAssets.ts +++ b/plugins/app-backend/src/lib/assets/findStaticAssets.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import globby from 'globby'; import { StaticAssetInput } from './types'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; /** * Finds all static assets within a directory diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index af88f0d9da..f1fb20a4b5 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -17,8 +17,8 @@ import { notFoundHandler, PluginDatabaseManager, - resolvePackagePath, } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { AppConfig, Config } from '@backstage/config'; import helmet from 'helmet'; import express from 'express'; diff --git a/plugins/auth-backend/src/database/AuthDatabase.ts b/plugins/auth-backend/src/database/AuthDatabase.ts index 97e4236754..ec4d2034e7 100644 --- a/plugins/auth-backend/src/database/AuthDatabase.ts +++ b/plugins/auth-backend/src/database/AuthDatabase.ts @@ -17,8 +17,8 @@ import { DatabaseManager, PluginDatabaseManager, - resolvePackagePath, } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { Knex } from 'knex'; diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/migrations.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/migrations.ts index 940a82107b..08c581e77a 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/database/migrations.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/migrations.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolvePackagePath } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; import { DB_MIGRATIONS_TABLE } from './tables'; diff --git a/plugins/catalog-backend/src/database/migrations.ts b/plugins/catalog-backend/src/database/migrations.ts index ed3a7751b5..5139c74930 100644 --- a/plugins/catalog-backend/src/database/migrations.ts +++ b/plugins/catalog-backend/src/database/migrations.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { resolvePackagePath } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; export async function applyDatabaseMigrations(knex: Knex): Promise { diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index ae548a9892..f4bd25d1ba 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - PluginDatabaseManager, - resolvePackagePath, -} from '@backstage/backend-common'; +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { NotificationGetOptions, NotificationModifyOptions, diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index 1a49ab0855..d170acb5d0 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -14,11 +14,8 @@ * limitations under the License. */ -import { - ContainerRunner, - UrlReader, - resolveSafeChildPath, -} from '@backstage/backend-common'; +import { ContainerRunner, UrlReader } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index a72ec8345b..5efcc6e0bb 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -27,11 +27,13 @@ import { } from '@backstage/plugin-scaffolder-node'; import { Octokit } from 'octokit'; import { CustomErrorBase, InputError } from '@backstage/errors'; -import { resolveSafeChildPath } from '@backstage/backend-common'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { getOctokitOptions } from './helpers'; import { examples } from './githubPullRequest.examples'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + resolveSafeChildPath, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; export type Encoding = 'utf-8' | 'base64'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index c9b8ce68a9..a549585426 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -23,7 +23,7 @@ import { Types } from '@gitbeaker/core'; import path from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index aae3b05c49..c926fe2971 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -23,7 +23,7 @@ import { Types } from '@gitbeaker/core'; import path from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi } from './helpers'; import { examples } from './gitlabRepoPush.examples'; diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index 14a58e4975..6860902c78 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -15,7 +15,7 @@ */ import { Isolate } from 'isolated-vm'; -import { resolvePackagePath } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { TemplateFilter as _TemplateFilter, TemplateGlobal as _TemplateGlobal, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts index 27c39dd098..9feaa14c07 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import * as yaml from 'yaml'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { z } from 'zod'; import { examples } from './write.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index 8bca630af9..c6beaa2d67 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { UrlReader, resolveSafeChildPath } from '@backstage/backend-common'; +import { UrlReader } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plain.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts index da0255ff5a..92db7aa3ed 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { UrlReader, resolveSafeChildPath } from '@backstage/backend-common'; +import { UrlReader } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plainFile.examples'; import { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts index 36f1f00b50..02fe0c30a4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts @@ -16,7 +16,8 @@ import { join as joinPath, sep as pathSep } from 'path'; import fs from 'fs-extra'; -import { resolvePackagePath, UrlReader } from '@backstage/backend-common'; +import { UrlReader } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createFetchTemplateAction } from './template'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index fd0ef7d757..0542b6a50d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -21,7 +21,8 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { join as joinPath, sep as pathSep } from 'path'; import fs from 'fs-extra'; -import { resolvePackagePath, UrlReader } from '@backstage/backend-common'; +import { UrlReader } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchTemplateAction } from './template'; import { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index b39582e18b..0983199339 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -15,7 +15,8 @@ */ import { extname } from 'path'; -import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common'; +import { UrlReader } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts index 5787d245ab..13dfc69525 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts @@ -16,7 +16,7 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { InputError } from '@backstage/errors'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import fs from 'fs-extra'; import { examples } from './delete.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts index 5d9d666d15..238fc9e8c0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts @@ -15,7 +15,7 @@ */ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import fs from 'fs-extra'; import { examples } from './rename.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index 2eaa505c8c..6b8caf5a3d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -33,9 +33,11 @@ import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; import fs from 'fs-extra'; -import { resolveSafeChildPath } from '@backstage/backend-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { + BackstageCredentials, + resolveSafeChildPath, +} from '@backstage/backend-plugin-api'; interface DryRunInput { spec: TaskSpec; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index f6f00b0441..9c95c11017 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -15,10 +15,8 @@ */ import { JsonObject } from '@backstage/types'; -import { - PluginDatabaseManager, - resolvePackagePath, -} from '@backstage/backend-common'; +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { ConflictError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; import { v4 as uuid } from 'uuid'; diff --git a/plugins/scaffolder-node/src/actions/fetch.ts b/plugins/scaffolder-node/src/actions/fetch.ts index 6b3e08bc7e..63bc4b1352 100644 --- a/plugins/scaffolder-node/src/actions/fetch.ts +++ b/plugins/scaffolder-node/src/actions/fetch.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common'; +import { UrlReader } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import fs from 'fs-extra'; diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts index 7f7121ef16..2b3a1296bf 100644 --- a/plugins/scaffolder-node/src/actions/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -15,7 +15,7 @@ */ import { InputError } from '@backstage/errors'; -import { isChildPath } from '@backstage/backend-common'; +import { isChildPath } from '@backstage/backend-plugin-api'; import { join as joinPath, normalize as normalizePath } from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; diff --git a/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts index bdcecd99f1..4c6ae251ee 100644 --- a/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts +++ b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import { dirname } from 'path'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { SerializedFile } from './types'; /** diff --git a/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts index 7e52f6323b..1b83a8cf32 100644 --- a/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts +++ b/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts @@ -17,7 +17,7 @@ import { promises as fs } from 'fs'; import globby from 'globby'; import limiterFactory from 'p-limit'; -import { resolveSafeChildPath } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { SerializedFile } from './types'; import { isError } from '@backstage/errors'; diff --git a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts index 3019c63a64..88ba7072dd 100644 --- a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts +++ b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - PluginDatabaseManager, - resolvePackagePath, -} from '@backstage/backend-common'; +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { Knex } from 'knex'; import { diff --git a/plugins/techdocs-node/src/helpers.ts b/plugins/techdocs-node/src/helpers.ts index 39a1f4c666..c293d16896 100644 --- a/plugins/techdocs-node/src/helpers.ts +++ b/plugins/techdocs-node/src/helpers.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common'; +import { UrlReader } from '@backstage/backend-common'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { Entity, getEntitySourceLocation, diff --git a/plugins/techdocs-node/src/stages/generate/helpers.ts b/plugins/techdocs-node/src/stages/generate/helpers.ts index 1450a4afba..df1a31470d 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { isChildPath } from '@backstage/backend-common'; +import { isChildPath } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { assertError, ForwardedError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; diff --git a/plugins/techdocs-node/src/stages/publish/local.ts b/plugins/techdocs-node/src/stages/publish/local.ts index 1a02cb7eee..37f41080b4 100644 --- a/plugins/techdocs-node/src/stages/publish/local.ts +++ b/plugins/techdocs-node/src/stages/publish/local.ts @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { - PluginEndpointDiscovery, resolvePackagePath, resolveSafeChildPath, -} from '@backstage/backend-common'; +} from '@backstage/backend-plugin-api'; import { Entity, CompoundEntityRef, diff --git a/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts b/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts index b0fe8fdecc..99c4b2cff4 100644 --- a/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts +++ b/plugins/user-settings-backend/src/database/DatabaseUserSettingsStore.ts @@ -14,10 +14,8 @@ * limitations under the License. */ -import { - PluginDatabaseManager, - resolvePackagePath, -} from '@backstage/backend-common'; +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { NotFoundError } from '@backstage/errors'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; diff --git a/yarn.lock b/yarn.lock index 9c9a730679..53cf1fef48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3506,8 +3506,11 @@ __metadata: resolution: "@backstage/backend-plugin-api@workspace:packages/backend-plugin-api" dependencies: "@backstage/backend-tasks": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/cli-common": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/types": "workspace:^" @@ -7669,7 +7672,7 @@ __metadata: dependencies: "@apidevtools/swagger-parser": ^10.1.0 "@apisyouwonthate/style-guide": ^1.4.0 - "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" From 01dfcf5742a245ea093645843ccf0c2d0bf36a0f Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 2 May 2024 15:58:45 +0200 Subject: [PATCH 373/567] docs: update api-reports Signed-off-by: Camila Belo --- packages/backend-common/api-report.md | 15 +++++++++------ packages/backend-plugin-api/api-report.md | 9 +++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index cccf326b9f..56debad5e2 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -34,7 +34,7 @@ import { HarnessIntegration } from '@backstage/integration'; import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; -import { isChildPath } from '@backstage/cli-common'; +import { isChildPath as isChildPath_2 } from '@backstage/backend-plugin-api'; import { KubeConfig } from '@kubernetes/client-node'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoadConfigOptionsRemote } from '@backstage/config-loader'; @@ -55,6 +55,8 @@ import { ReadTreeResponseFile } from '@backstage/backend-plugin-api'; import { ReadUrlOptions } from '@backstage/backend-plugin-api'; import { ReadUrlResponse } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; +import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-plugin-api'; +import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import { SchedulerService } from '@backstage/backend-plugin-api'; @@ -533,7 +535,8 @@ export class HarnessUrlReader implements UrlReader { // @public export const HostDiscovery: typeof HostDiscovery_2; -export { isChildPath }; +// @public @deprecated (undocumented) +export const isChildPath: typeof isChildPath_2; // @public export function isDatabaseConflictError(e: unknown): boolean; @@ -738,11 +741,11 @@ export type RequestLoggingHandlerFactory = ( logger?: LoggerService, ) => RequestHandler; -// @public -export function resolvePackagePath(name: string, ...paths: string[]): string; +// @public @deprecated (undocumented) +export const resolvePackagePath: typeof resolvePackagePath_2; -// @public -export function resolveSafeChildPath(base: string, path: string): string; +// @public @deprecated (undocumented) +export const resolveSafeChildPath: typeof resolveSafeChildPath_2; // @public export type RunContainerOptions = { diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index b04c7bc20b..932ccc74ab 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -10,6 +10,7 @@ import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common import { Config } from '@backstage/config'; import { Handler } from 'express'; import { IdentityApi } from '@backstage/plugin-auth-node'; +import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; @@ -351,6 +352,8 @@ export interface HttpRouterServiceAuthPolicy { // @public (undocumented) export interface IdentityService extends IdentityApi {} +export { isChildPath }; + // @public (undocumented) export interface LifecycleService { addShutdownHook( @@ -496,6 +499,12 @@ export type ReadUrlResponse = { lastModifiedAt?: Date; }; +// @public +export function resolvePackagePath(name: string, ...paths: string[]): string; + +// @public +export function resolveSafeChildPath(base: string, path: string): string; + // @public (undocumented) export interface RootConfigService extends Config {} From f09848a2ef645c2977a2bfe2a45f445505a336f3 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 13 May 2024 14:17:43 +0200 Subject: [PATCH 374/567] Update .changeset/funny-cars-jump.md Co-authored-by: Patrik Oldsberg Signed-off-by: Camila Belo --- .changeset/funny-cars-jump.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/funny-cars-jump.md b/.changeset/funny-cars-jump.md index cbaf01c7d0..c67204d68c 100644 --- a/.changeset/funny-cars-jump.md +++ b/.changeset/funny-cars-jump.md @@ -21,4 +21,4 @@ '@backstage/plugin-app-backend': patch --- -Extract path utilities from `backend-common` to the `backend-plugin-api` package. +Move path utilities from `backend-common` to the `backend-plugin-api` package. From 009da479d54861114771c6279738cf6313f91878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Mon, 22 Apr 2024 14:16:52 +0200 Subject: [PATCH 375/567] fix: Fix versions-check cli command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .changeset/wild-doors-cheat.md | 5 ++ packages/cli-node/package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/src/lib/versioning/Lockfile.ts | 80 +++++++++++++++++---- plugins/devtools-backend/package.json | 2 +- yarn.lock | 14 ++-- 6 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 .changeset/wild-doors-cheat.md diff --git a/.changeset/wild-doors-cheat.md b/.changeset/wild-doors-cheat.md new file mode 100644 index 0000000000..c302dde467 --- /dev/null +++ b/.changeset/wild-doors-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix `versions:check --fix` when `yarn.lock` has multiple joint versions in the same section diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index 3bd9a98f60..a786d71949 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -35,7 +35,7 @@ "@backstage/errors": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@yarnpkg/parsers": "^3.0.0-rc.4", + "@yarnpkg/parsers": "^3.0.0", "fs-extra": "^11.2.0", "semver": "^7.5.3", "zod": "^3.22.4" diff --git a/packages/cli/package.json b/packages/cli/package.json index d6fe116248..02a57980e9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -79,7 +79,7 @@ "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.7.2", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "^3.0.0-rc.4", + "@yarnpkg/parsers": "^3.0.0", "bfj": "^8.0.0", "buffer": "^6.0.3", "chalk": "^4.0.0", diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 50d0a77cfd..6af115a4da 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -98,6 +98,24 @@ export class Lockfile { return Lockfile.parse(lockfileContents); } + static #getRangesFromDataKey(key: string): string[] { + const [, name, ranges] = key.match(ENTRY_PATTERN) ?? []; + if (!name) { + throw new Error(`Failed to parse yarn.lock entry '${key}'`); + } + + return ranges.split(/\s*,\s*/).map(rangePart => { + let range = rangePart; + if (range.startsWith(`${name}@`)) { + range = range.slice(`${name}@`.length); + } + if (range.startsWith('npm:')) { + range = range.slice('npm:'.length); + } + return range; + }); + } + static parse(content: string) { const legacy = LEGACY_REGEX.test(content); @@ -113,7 +131,7 @@ export class Lockfile { for (const [key, value] of Object.entries(data)) { if (SPECIAL_OBJECT_KEYS.includes(key)) continue; - const [, name, ranges] = ENTRY_PATTERN.exec(key) ?? []; + const [, name] = ENTRY_PATTERN.exec(key) ?? []; if (!name) { throw new Error(`Failed to parse yarn.lock entry '${key}'`); } @@ -123,13 +141,8 @@ export class Lockfile { queries = []; packages.set(name, queries); } - for (let range of ranges.split(/\s*,\s*/)) { - if (range.startsWith(`${name}@`)) { - range = range.slice(`${name}@`.length); - } - if (range.startsWith('npm:')) { - range = range.slice('npm:'.length); - } + const ranges = Lockfile.#getRangesFromDataKey(key); + for (const range of ranges) { queries.push({ range, version: value.version, dataKey: key }); } } @@ -276,9 +289,34 @@ export class Lockfile { } remove(name: string, range: string): boolean { - const query = `${name}@${range}`; + const simpleQuery = `${name}@${range}`; + const query = this.getEntryOf(name, range); + const existed = Boolean(this.data[query]); - delete this.data[query]; + + if (simpleQuery === query) { + // Single-versioned entry, just delete + delete this.data[query]; + } else { + // Remove this version from the entry key. This modifies the key, so the + // package queries' needs to be updated too. + const newRanges = Lockfile.#getRangesFromDataKey(query).filter( + q => q !== simpleQuery, + ); + const newQuery = newRanges.join(', '); + + // Replace the entry with a new one without this particular range + const entry = this.data[query]; + delete this.data[query]; + this.data[newQuery] = entry; + + // Fix all package queries pointing to the old query + this.packages.get(name)?.forEach(q => { + if (q.dataKey === query) { + q.dataKey = newQuery; + } + }); + } const newEntries = this.packages.get(name)?.filter(e => e.range !== range); if (newEntries) { @@ -288,19 +326,35 @@ export class Lockfile { return existed; } + getEntryOf(name: string, range: string) { + const query = this.packages.get(name)?.find(q => q.range === range); + if (!query) { + throw new Error(`No entry data for ${name}@${range}`); + } + + return query.dataKey; + } + /** Modifies the lockfile by bumping packages to the suggested versions */ replaceVersions(results: AnalyzeResultNewVersion[]) { + // When replacing versions, we might replace the same version multiple times, + // as a query may contain multiple versions. This keeps the original version, + // to ensure we don't make mistakes. + const oldVersions = Object.fromEntries( + Object.entries(this.data).map(([key, val]) => [key, val.version]), + ); + for (const { name, range, oldVersion, newVersion } of results) { - const query = `${name}@${range}`; + const query = this.getEntryOf(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) { + if (oldVersions[query] !== oldVersion) { throw new Error( - `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, + `Expected existing version data for ${query} to be ${oldVersion}, was ${oldVersions[query]}`, ); } diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index fd2c8eac4a..6c440b66f0 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -41,7 +41,7 @@ "@manypkg/get-packages": "^1.1.3", "@types/express": "*", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "^3.0.0-rc.4", + "@yarnpkg/parsers": "^3.0.0", "express": "^4.18.1", "express-promise-router": "^4.1.0", "fs-extra": "^11.0.0", diff --git a/yarn.lock b/yarn.lock index 9c9a730679..bcf0790899 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3642,7 +3642,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/types": "workspace:^" "@manypkg/get-packages": ^1.1.3 - "@yarnpkg/parsers": ^3.0.0-rc.4 + "@yarnpkg/parsers": ^3.0.0 fs-extra: ^11.2.0 semver: ^7.5.3 zod: ^3.22.4 @@ -3716,7 +3716,7 @@ __metadata: "@typescript-eslint/parser": ^6.7.2 "@vitejs/plugin-react": ^4.0.4 "@yarnpkg/lockfile": ^1.1.0 - "@yarnpkg/parsers": ^3.0.0-rc.4 + "@yarnpkg/parsers": ^3.0.0 bfj: ^8.0.0 buffer: ^6.0.3 chalk: ^4.0.0 @@ -5777,7 +5777,7 @@ __metadata: "@types/supertest": ^2.0.8 "@types/yarnpkg__lockfile": ^1.1.4 "@yarnpkg/lockfile": ^1.1.0 - "@yarnpkg/parsers": ^3.0.0-rc.4 + "@yarnpkg/parsers": ^3.0.0 express: ^4.18.1 express-promise-router: ^4.1.0 fs-extra: ^11.0.0 @@ -19325,13 +19325,13 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/parsers@npm:^3.0.0-rc.4": - version: 3.0.0-rc.21 - resolution: "@yarnpkg/parsers@npm:3.0.0-rc.21" +"@yarnpkg/parsers@npm:^3.0.0": + version: 3.0.0 + resolution: "@yarnpkg/parsers@npm:3.0.0" dependencies: js-yaml: ^3.10.0 tslib: ^2.4.0 - checksum: c0741ef01089a7d452dfb75b8c24a82ddcc3e218dac22b794a06f2e50c7070378c460f63f63fb7ed0f62f634114e448e3d85a75ccd4db84e1526242ae9b4a7d5 + checksum: fefe5ecafb5bfa2b678ac9ba9259810fdda40142afd9d0b7e0e5cc1cce1fd824dffc52217c5e429807481d8fd18ead074bd317e64fd626335d3c9f1a320bade2 languageName: node linkType: hard From f7edc833204e8ccdf79cc676af906b80a1112ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Mon, 22 Apr 2024 16:12:41 +0200 Subject: [PATCH 376/567] Revert package upgrade from devtools-backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- plugins/devtools-backend/package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 6c440b66f0..fd2c8eac4a 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -41,7 +41,7 @@ "@manypkg/get-packages": "^1.1.3", "@types/express": "*", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "^3.0.0", + "@yarnpkg/parsers": "^3.0.0-rc.4", "express": "^4.18.1", "express-promise-router": "^4.1.0", "fs-extra": "^11.0.0", diff --git a/yarn.lock b/yarn.lock index bcf0790899..f3b60bc781 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5777,7 +5777,7 @@ __metadata: "@types/supertest": ^2.0.8 "@types/yarnpkg__lockfile": ^1.1.4 "@yarnpkg/lockfile": ^1.1.0 - "@yarnpkg/parsers": ^3.0.0 + "@yarnpkg/parsers": ^3.0.0-rc.4 express: ^4.18.1 express-promise-router: ^4.1.0 fs-extra: ^11.0.0 @@ -19325,7 +19325,7 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/parsers@npm:^3.0.0": +"@yarnpkg/parsers@npm:^3.0.0, @yarnpkg/parsers@npm:^3.0.0-rc.4": version: 3.0.0 resolution: "@yarnpkg/parsers@npm:3.0.0" dependencies: From 17ed7bc6f92ca10c4697013b0420548384ab0890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Tue, 23 Apr 2024 06:08:32 +0200 Subject: [PATCH 377/567] Back to the future MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- plugins/devtools-backend/package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index fd2c8eac4a..6c440b66f0 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -41,7 +41,7 @@ "@manypkg/get-packages": "^1.1.3", "@types/express": "*", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "^3.0.0-rc.4", + "@yarnpkg/parsers": "^3.0.0", "express": "^4.18.1", "express-promise-router": "^4.1.0", "fs-extra": "^11.0.0", diff --git a/yarn.lock b/yarn.lock index f3b60bc781..bcf0790899 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5777,7 +5777,7 @@ __metadata: "@types/supertest": ^2.0.8 "@types/yarnpkg__lockfile": ^1.1.4 "@yarnpkg/lockfile": ^1.1.0 - "@yarnpkg/parsers": ^3.0.0-rc.4 + "@yarnpkg/parsers": ^3.0.0 express: ^4.18.1 express-promise-router: ^4.1.0 fs-extra: ^11.0.0 @@ -19325,7 +19325,7 @@ __metadata: languageName: node linkType: hard -"@yarnpkg/parsers@npm:^3.0.0, @yarnpkg/parsers@npm:^3.0.0-rc.4": +"@yarnpkg/parsers@npm:^3.0.0": version: 3.0.0 resolution: "@yarnpkg/parsers@npm:3.0.0" dependencies: From 93be042a86d12ad22435599ebd904993f06e606a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Mon, 13 May 2024 14:34:58 +0200 Subject: [PATCH 378/567] fix: Added changeset to cli-node dependency upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .changeset/breezy-badgers-train.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/breezy-badgers-train.md diff --git a/.changeset/breezy-badgers-train.md b/.changeset/breezy-badgers-train.md new file mode 100644 index 0000000000..8f747b8602 --- /dev/null +++ b/.changeset/breezy-badgers-train.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-node': patch +--- + +Upgraded @yarnpkg/parsers to stable 3.0 From 4668dc76c97150de63d108f7c17b05b2a10cffe6 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Mon, 13 May 2024 18:50:50 +0530 Subject: [PATCH 379/567] variable name change Signed-off-by: npiyush97 --- .../src/modules/core/AnnotateLocationEntityProcessor.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index 83638d205f..f46f5cfd42 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -53,14 +53,14 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; - const gitCommitBranchURLPattern = /\b[0-9a-f]{40,}\b/; + const commitHashRegExp = /\b[0-9a-f]{40,}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); viewUrl = location.target; - if (!gitCommitBranchURLPattern.test(location.target)) { + if (!commitHashRegExp.test(location.target)) { editUrl = scmIntegration?.resolveEditUrl(location.target); } From d871c5627acc58e5b2bd7f9b7c4465eb40665bc0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 13 May 2024 17:34:32 +0200 Subject: [PATCH 380/567] refactor: apply review suggestions Signed-off-by: Camila Belo --- packages/backend-common/src/deprecated.ts | 28 ----------------- packages/backend-common/src/index.ts | 6 +--- packages/backend-common/src/testUtils.ts | 32 +++++++++++++++++--- packages/backend-plugin-api/src/testUtils.ts | 2 -- 4 files changed, 28 insertions(+), 40 deletions(-) diff --git a/packages/backend-common/src/deprecated.ts b/packages/backend-common/src/deprecated.ts index cee15aadb6..4589303e4b 100644 --- a/packages/backend-common/src/deprecated.ts +++ b/packages/backend-common/src/deprecated.ts @@ -20,34 +20,6 @@ import { isChildPath as _isChildPath, } from '@backstage/backend-plugin-api'; -import { - overridePackagePathResolution as _overridePackagePathResolution, - OverridePackagePathResolutionOptions as _OverridePackagePathResolutionOptions, - PackagePathResolutionOverride as _PackagePathResolutionOverride, -} from '@backstage/backend-plugin-api/testUtils'; - -/** - * @public - * @deprecated This function is deprecated and will be removed in future release, see https://github.com/backstage/backstage/issues/24493. - * Please use the `overridePackagePathResolution` function from the `@backstage/backend-plugin-api/testUtils` package instead. - */ -export const overridePackagePathResolution = _overridePackagePathResolution; - -/** - * @public - * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. - * Please use the `OverridePackagePathResolutionOptions` type from the `@backstage/backend-plugin-api/testUtils` package instead. - */ -export type OverridePackagePathResolutionOptions = - _OverridePackagePathResolutionOptions; - -/** - * @public - * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. - * Please use the `PackagePathResolutionOverride` type from the `@backstage/backend-plugin-api/testUtils` package instead. - */ -export type PackagePathResolutionOverride = _PackagePathResolutionOverride; - /** * @public * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 1d01410408..0c435ad9de 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -25,11 +25,7 @@ export type { LegacyCreateRouter } from './legacy'; export * from './auth'; export * from './cache'; export { loadBackendConfig } from './config'; -export { - resolvePackagePath, - resolveSafeChildPath, - isChildPath, -} from './deprecated'; +export * from './deprecated'; export * from './database'; export * from './discovery'; export * from './hot'; diff --git a/packages/backend-common/src/testUtils.ts b/packages/backend-common/src/testUtils.ts index bae633428f..397d97f20e 100644 --- a/packages/backend-common/src/testUtils.ts +++ b/packages/backend-common/src/testUtils.ts @@ -14,8 +14,30 @@ * limitations under the License. */ -export { - overridePackagePathResolution, - type OverridePackagePathResolutionOptions, - type PackagePathResolutionOverride, -} from './deprecated'; +import { + overridePackagePathResolution as _overridePackagePathResolution, + OverridePackagePathResolutionOptions as _OverridePackagePathResolutionOptions, + PackagePathResolutionOverride as _PackagePathResolutionOverride, +} from '@backstage/backend-plugin-api/testUtils'; + +/** + * @public + * @deprecated This function is deprecated and will be removed in future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `overridePackagePathResolution` function from the `@backstage/backend-plugin-api/testUtils` package instead. + */ +export const overridePackagePathResolution = _overridePackagePathResolution; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `OverridePackagePathResolutionOptions` type from the `@backstage/backend-plugin-api/testUtils` package instead. + */ +export type OverridePackagePathResolutionOptions = + _OverridePackagePathResolutionOptions; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `PackagePathResolutionOverride` type from the `@backstage/backend-plugin-api/testUtils` package instead. + */ +export type PackagePathResolutionOverride = _PackagePathResolutionOverride; diff --git a/packages/backend-plugin-api/src/testUtils.ts b/packages/backend-plugin-api/src/testUtils.ts index ba09145bf0..9616ab1701 100644 --- a/packages/backend-plugin-api/src/testUtils.ts +++ b/packages/backend-plugin-api/src/testUtils.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -// TODO: Remove this relative import when extrating the path utilities to this package -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { packagePathMocks } from './paths'; import { posix as posixPath, resolve as resolvePath } from 'path'; From ef716d9eeffe92061639269e0e190854ce1cba77 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 16:20:27 +0000 Subject: [PATCH 381/567] fix(deps): update dependency @pmmmwh/react-refresh-webpack-plugin to v0.5.13 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 58469b81a6..0f57532af5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13337,14 +13337,12 @@ __metadata: linkType: hard "@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.7": - version: 0.5.11 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.11" + version: 0.5.13 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.13" dependencies: ansi-html-community: ^0.0.8 - common-path-prefix: ^3.0.0 core-js-pure: ^3.23.3 error-stack-parser: ^2.0.6 - find-up: ^5.0.0 html-entities: ^2.1.0 loader-utils: ^2.0.4 schema-utils: ^3.0.0 @@ -13355,7 +13353,7 @@ __metadata: sockjs-client: ^1.4.0 type-fest: ">=0.17.0 <5.0.0" webpack: ">=4.43.0 <6.0.0" - webpack-dev-server: 3.x || 4.x + webpack-dev-server: 3.x || 4.x || 5.x webpack-hot-middleware: 2.x webpack-plugin-serve: 0.x || 1.x peerDependenciesMeta: @@ -13371,7 +13369,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: a82eced9519f4dcac424acae719f819ab4150bfcf2874ac7daaf25a4f1c409e3d8b9d693fea0c686c24d520a5473756df32da90d8b89739670f8f8084c600bb4 + checksum: 9f931cf79945f58ee31569b83f4b294ae0849ea8232b6c79e690b46a3d7f2b981aa72718a4bd7517ab82657dddfed2a691c9d9e37295a87dfd0b18b2693d4aa6 languageName: node linkType: hard @@ -22253,13 +22251,6 @@ __metadata: languageName: node linkType: hard -"common-path-prefix@npm:^3.0.0": - version: 3.0.0 - resolution: "common-path-prefix@npm:3.0.0" - checksum: fdb3c4f54e51e70d417ccd950c07f757582de800c0678ca388aedefefc84982039f346f9fd9a1252d08d2da9e9ef4019f580a1d1d3a10da031e4bb3c924c5818 - languageName: node - linkType: hard - "common-tags@npm:^1.8.0": version: 1.8.2 resolution: "common-tags@npm:1.8.2" From c889f9141381dabba1573edc85c3a5b1290b3365 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 17:05:59 +0000 Subject: [PATCH 382/567] fix(deps): update dependency @types/passport-oauth2 to v1.4.16 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0f57532af5..99d5075482 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17916,13 +17916,13 @@ __metadata: linkType: hard "@types/passport-oauth2@npm:*, @types/passport-oauth2@npm:^1.4.11, @types/passport-oauth2@npm:^1.4.15": - version: 1.4.15 - resolution: "@types/passport-oauth2@npm:1.4.15" + version: 1.4.16 + resolution: "@types/passport-oauth2@npm:1.4.16" dependencies: "@types/express": "*" "@types/oauth": "*" "@types/passport": "*" - checksum: 352c4e2d09a86f8fc0dcf2c917c221f302a35a14e7467e2fa3a1653c0a9a1f842c910a4e52f799856536522e6359c20d1afc319579d9edf4b2ac7ea11b183b52 + checksum: a590d3fcdf93dbe4498c636de139dc22a723bbf12a7f248f432085a47ec1b76b2e1201336ee26cd65c400d7ce6fb30ae0b151f40067267942c8a5b3c708cc23d languageName: node linkType: hard From 81a215d3e694b7814839a0fdef5b9500ce5e9833 Mon Sep 17 00:00:00 2001 From: npiyush97 Date: Mon, 13 May 2024 22:44:00 +0530 Subject: [PATCH 383/567] added changes Signed-off-by: npiyush97 --- .changeset/thirty-plums-shout.md | 2 +- .../src/modules/core/AnnotateLocationEntityProcessor.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/thirty-plums-shout.md b/.changeset/thirty-plums-shout.md index 5b5ffad6a9..56ac640326 100644 --- a/.changeset/thirty-plums-shout.md +++ b/.changeset/thirty-plums-shout.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend': patch --- -Added a regex test to check commit hash.If url is from git commit branch ignore the edit url. +Added a regex test to check commit hash. If url is from git commit branch ignore the edit url. diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index f46f5cfd42..00864b69e7 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -31,6 +31,7 @@ import { CatalogProcessorEmit, } from '@backstage/plugin-catalog-node'; +const commitHashRegExp = /\b[0-9a-f]{40,}\b/; /** @public */ export class AnnotateLocationEntityProcessor implements CatalogProcessor { constructor( @@ -53,7 +54,6 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { let viewUrl; let editUrl; let sourceLocation; - const commitHashRegExp = /\b[0-9a-f]{40,}\b/; if (location.type === 'url') { const scmIntegration = integrations.byUrl(location.target); From 03cdff9c3c3dfa52539d572901ecbb33286e42ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 17:58:49 +0000 Subject: [PATCH 384/567] fix(deps): update dependency @useoptic/openapi-utilities to v0.54.13 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 99d5075482..cf61ceace7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18896,13 +18896,13 @@ __metadata: languageName: node linkType: hard -"@useoptic/json-pointer-helpers@npm:0.54.8": - version: 0.54.8 - resolution: "@useoptic/json-pointer-helpers@npm:0.54.8" +"@useoptic/json-pointer-helpers@npm:0.54.13": + version: 0.54.13 + resolution: "@useoptic/json-pointer-helpers@npm:0.54.13" dependencies: jsonpointer: ^5.0.1 minimatch: 9.0.3 - checksum: 4eddabb6dce3ca8160dcd4904299b6964945c3fe47d39bfeca6c68b9a50b058b901a6fb10ab168295475d651df3349149faa5f27f77293e15b6eee8d4417432e + checksum: ad987e9bbec82606bd5995f4ffea7eb708549573e8a94a201ed266e4efc854423804671c3772be714457c417641da69d6689a1e61164bd0cfb8f83a1561facdb languageName: node linkType: hard @@ -18961,10 +18961,10 @@ __metadata: linkType: hard "@useoptic/openapi-utilities@npm:^0.54.8": - version: 0.54.8 - resolution: "@useoptic/openapi-utilities@npm:0.54.8" + version: 0.54.13 + resolution: "@useoptic/openapi-utilities@npm:0.54.13" dependencies: - "@useoptic/json-pointer-helpers": 0.54.8 + "@useoptic/json-pointer-helpers": 0.54.13 ajv: ^8.6.0 ajv-errors: ~3.0.0 ajv-formats: ~2.1.0 @@ -18981,7 +18981,7 @@ __metadata: ts-invariant: ^0.9.3 url-join: ^4.0.1 yaml-ast-parser: ^0.0.43 - checksum: fa9e9f430c77687591aaf8b43b7b31a7c2f80fe9c140aaa978f1948f84d3e974181c91c3d8ec3e06efca9735c7826290baf4be72063bf733887aa632b40c3c4a + checksum: d02eabba96af29632557cdb5532edcc4a629860471fe6cd02990c54ad8014c980d07e3dde8d04ed644a0bd3c2218a93fc50fe9904ee317ef6189b2db41bc7505 languageName: node linkType: hard From 5b1ecde6142e2c8d01cc215aca7fac3fb6edda57 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 18:42:26 +0000 Subject: [PATCH 385/567] fix(deps): update dependency clsx to v2.1.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- microsite/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 3ca3e2837a..572a8fe5fd 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -4334,9 +4334,9 @@ __metadata: linkType: hard "clsx@npm:^2.0.0": - version: 2.1.0 - resolution: "clsx@npm:2.1.0" - checksum: 43fefc29b6b49c9476fbce4f8b1cc75c27b67747738e598e6651dd40d63692135dc60b18fa1c5b78a2a9ba8ae6fd2055a068924b94e20b42039bd53b78b98e1d + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: acd3e1ab9d8a433ecb3cc2f6a05ab95fe50b4a3cfc5ba47abb6cbf3754585fcb87b84e90c822a1f256c4198e3b41c7f6c391577ffc8678ad587fc0976b24fd57 languageName: node linkType: hard From 5f3f8a86fd1d4ed7305833013f49f96cd1b40d84 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 13 May 2024 21:59:12 +0200 Subject: [PATCH 386/567] catalog-github: too much css Signed-off-by: Vincenzo Scamporlino --- ...ntityCleanerProvider.tsx => GithubOrgEntityCleanerProvider.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/catalog-backend-module-github-org/src/{GithubOrgEntityCleanerProvider.tsx => GithubOrgEntityCleanerProvider.ts} (100%) diff --git a/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx b/plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.ts similarity index 100% rename from plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.tsx rename to plugins/catalog-backend-module-github-org/src/GithubOrgEntityCleanerProvider.ts From 789aa030b374823fd36e82249141dc7f80082620 Mon Sep 17 00:00:00 2001 From: Erik Sjoholm Date: Mon, 13 May 2024 16:45:51 -0700 Subject: [PATCH 387/567] fix a bug documented in issue#24753 where markdown descriptions are not being opened in a new tab Signed-off-by: Erik Sjoholm --- .../src/next/components/ScaffolderField/ScaffolderField.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx index 1bb1656fd0..60c3907ddb 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx @@ -76,6 +76,7 @@ export const ScaffolderField = ( {displayLabel && rawDescription ? ( ) : null} From 86dc29dcbe0bcd5bf225036c37ca0ed9d9268c0e Mon Sep 17 00:00:00 2001 From: Erik Sjoholm Date: Mon, 13 May 2024 16:52:00 -0700 Subject: [PATCH 388/567] Add changeset and api-report Signed-off-by: Erik Sjoholm --- .changeset/loud-pumpkins-bow.md | 5 +++++ plugins/scaffolder-backend/api-report.md | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .changeset/loud-pumpkins-bow.md diff --git a/.changeset/loud-pumpkins-bow.md b/.changeset/loud-pumpkins-bow.md new file mode 100644 index 0000000000..366645db24 --- /dev/null +++ b/.changeset/loud-pumpkins-bow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Fixed a bug where links in scaffolder field markdown content e.g. - description was not able to be launched in a new tab. This fix makes it so that all links within Scaffolder Fields are launched in a new tab. diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index c141733ec4..8a9623d78f 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -302,7 +302,10 @@ export const createPublishGitlabMergeRequestAction: (options: { }) => TemplateAction_2< { repoUrl: string; - title: string; + title: string + /** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */; description: string; branchName: string; targetBranchName?: string | undefined; From e49a8106ad27ba869efec9d5e63e12b00254fbd0 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 14 May 2024 09:55:07 +0300 Subject: [PATCH 389/567] fix: show all notifications by default in notifications page to prevent confusion with sidebar item showing that you have new notifications but the table not showing any because the default filter is for last week only Signed-off-by: Heikki Hellgren --- .changeset/nice-scissors-jog.md | 5 +++++ .../src/components/NotificationsPage/NotificationsPage.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/nice-scissors-jog.md diff --git a/.changeset/nice-scissors-jog.md b/.changeset/nice-scissors-jog.md new file mode 100644 index 0000000000..7065a9e63f --- /dev/null +++ b/.changeset/nice-scissors-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Show all notifications by default to match the sidebar item status diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index d1dde185f2..ef1fda0407 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -71,7 +71,7 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { const [pageNumber, setPageNumber] = React.useState(0); const [pageSize, setPageSize] = React.useState(5); const [containsText, setContainsText] = React.useState(); - const [createdAfter, setCreatedAfter] = React.useState('lastWeek'); + const [createdAfter, setCreatedAfter] = React.useState('all'); const [sorting, setSorting] = React.useState( SortByOptions.newest.sortBy, ); From 120c2a49f3a4121bef10a5ff95c19042362b07e8 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 14 May 2024 11:05:23 +0200 Subject: [PATCH 390/567] refactor(backend-common): unify deprecated file and folder Signed-off-by: Camila Belo --- packages/backend-common/src/deprecated.ts | 42 ------------------- .../backend-common/src/deprecated/index.ts | 27 ++++++++++++ packages/backend-common/src/index.ts | 2 - 3 files changed, 27 insertions(+), 44 deletions(-) delete mode 100644 packages/backend-common/src/deprecated.ts diff --git a/packages/backend-common/src/deprecated.ts b/packages/backend-common/src/deprecated.ts deleted file mode 100644 index 4589303e4b..0000000000 --- a/packages/backend-common/src/deprecated.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2024 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - resolvePackagePath as _resolvePackagePath, - resolveSafeChildPath as _resolveSafeChildPath, - isChildPath as _isChildPath, -} from '@backstage/backend-plugin-api'; - -/** - * @public - * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. - * Please use the `resolvePackagePath` function from the `@backstage/backend-plugin-api` package instead. - */ -export const resolvePackagePath = _resolvePackagePath; - -/** - * @public - * @deprecated This type is deprecated and will be removed in a future release, see - * Please use the `resolveSafeChildPath` function from the `@backstage/backend-plugin-api` package instead. - */ -export const resolveSafeChildPath = _resolveSafeChildPath; - -/** - * @public - * @deprecated This type is deprecated and will be removed in a future release, see - * Please use the `isChildPath` function from the `@backstage/cli-common` package instead. - */ -export const isChildPath = _isChildPath; diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts index ed5fdcc577..708e358bfc 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -15,3 +15,30 @@ */ export * from './scm'; + +import { + resolvePackagePath as _resolvePackagePath, + resolveSafeChildPath as _resolveSafeChildPath, + isChildPath as _isChildPath, +} from '@backstage/backend-plugin-api'; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `resolvePackagePath` function from the `@backstage/backend-plugin-api` package instead. + */ +export const resolvePackagePath = _resolvePackagePath; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see + * Please use the `resolveSafeChildPath` function from the `@backstage/backend-plugin-api` package instead. + */ +export const resolveSafeChildPath = _resolveSafeChildPath; + +/** + * @public + * @deprecated This type is deprecated and will be removed in a future release, see + * Please use the `isChildPath` function from the `@backstage/cli-common` package instead. + */ +export const isChildPath = _isChildPath; diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 216eb9446e..87bfa245f6 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -22,8 +22,6 @@ export { legacyPlugin, makeLegacyPlugin } from './legacy'; export type { LegacyCreateRouter } from './legacy'; -export { Git } from './deprecated'; -export type { StaticAuthOptions, AuthCallbackOptions } from './deprecated'; export * from './auth'; export * from './cache'; export { loadBackendConfig } from './config'; From 5a8a01cf829a06a9804b109df59491075622206f Mon Sep 17 00:00:00 2001 From: Benjamin Janssens Date: Tue, 14 May 2024 11:40:14 +0200 Subject: [PATCH 391/567] feat: improve changesets Co-authored-by: Vincenzo Scamporlino Signed-off-by: Benjamin Janssens --- .changeset/gentle-baboons-peel.md | 4 ++-- .changeset/tiny-pandas-return.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/gentle-baboons-peel.md b/.changeset/gentle-baboons-peel.md index d63396860b..b51ad77283 100644 --- a/.changeset/gentle-baboons-peel.md +++ b/.changeset/gentle-baboons-peel.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-techdocs': minor +'@backstage/plugin-techdocs': patch --- -Added property ownerPickerMode to TechDocsIndexPage +`TechDocsIndexPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. diff --git a/.changeset/tiny-pandas-return.md b/.changeset/tiny-pandas-return.md index d584accccf..cc9210a368 100644 --- a/.changeset/tiny-pandas-return.md +++ b/.changeset/tiny-pandas-return.md @@ -2,4 +2,4 @@ '@backstage/plugin-api-docs': patch --- -Added property ownerPickerMode to DefaultApiExplorerPage +`DefaultApiExplorerPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. From 07ba7f648e01ddd946f4d4bf7c76fe558771bb8e Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 14 May 2024 11:44:52 +0200 Subject: [PATCH 392/567] docs: fix vale issue Signed-off-by: Vincenzo Scamporlino --- docs/integrations/github/org.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 94eb551c7c..7348c4cb21 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -90,7 +90,7 @@ Directly under the `githubOrg` is a list of configurations, each entry is a stru - `id`: A stable id for this provider. Entities from this provider will be associated with this ID, so you should take care not to change it over time since that may lead to orphaned entities and/or conflicts. - `githubUrl`: The target that this provider should consume -- `orgs` (optional): The list of the GitHub orgs to consume. By default wil consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). +- `orgs` (optional): The list of the GitHub orgs to consume. By default will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). - `schedule`: The refresh schedule to use, matches the structure of [`TaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinitionconfig/) ### Events Support From b19dfb38cceb21b2c165ebe397e0d9e4cda80e1a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 May 2024 11:54:07 +0200 Subject: [PATCH 393/567] docs/integration/github: doc spelling fix + update Signed-off-by: Patrik Oldsberg --- docs/integrations/github/org.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 7348c4cb21..06b4e07daa 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -90,7 +90,7 @@ Directly under the `githubOrg` is a list of configurations, each entry is a stru - `id`: A stable id for this provider. Entities from this provider will be associated with this ID, so you should take care not to change it over time since that may lead to orphaned entities and/or conflicts. - `githubUrl`: The target that this provider should consume -- `orgs` (optional): The list of the GitHub orgs to consume. By default will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). +- `orgs` (optional): The list of the GitHub orgs to consume. If you only list a single org the generated group entities will use the `default` namespace, otherwise they will use the org name as the namespace. By default the provider will consume all accessible orgs on the given GitHub instance (support for GitHub App integration only). - `schedule`: The refresh schedule to use, matches the structure of [`TaskScheduleDefinitionConfig`](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinitionconfig/) ### Events Support From 329cc345af42c7220a031bec82ed7c62d3415d99 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 May 2024 11:06:45 +0200 Subject: [PATCH 394/567] backend-app-api: add initialization logger Signed-off-by: Patrik Oldsberg --- .changeset/pink-nails-warn.md | 5 ++ .../src/wiring/BackendInitializer.ts | 10 +++ .../src/wiring/createInitializationLogger.ts | 79 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 .changeset/pink-nails-warn.md create mode 100644 packages/backend-app-api/src/wiring/createInitializationLogger.ts diff --git a/.changeset/pink-nails-warn.md b/.changeset/pink-nails-warn.md new file mode 100644 index 0000000000..6bdbf465b0 --- /dev/null +++ b/.changeset/pink-nails-warn.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Added logging of all plugins being initialized, periodic status, and completion. diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index fe949c2e33..1bf38a8c40 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -31,6 +31,7 @@ import { ForwardedError, ConflictError } from '@backstage/errors'; import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha'; import { DependencyGraph } from '../lib/DependencyGraph'; import { ServiceRegistry } from './ServiceRegistry'; +import { createInitializationLogger } from './createInitializationLogger'; export interface BackendRegisterInit { consumes: Set; @@ -226,6 +227,11 @@ export class BackendInitializer { const allPluginIds = [...pluginInits.keys()]; + const initLogger = createInitializationLogger( + allPluginIds, + await this.#serviceRegistry.get(coreServices.rootLogger, 'root'), + ); + // All plugins are initialized in parallel await Promise.all( allPluginIds.map(async pluginId => { @@ -289,6 +295,8 @@ export class BackendInitializer { }); } + initLogger.onPluginStarted(pluginId); + // Once the plugin and all modules have been initialized, we can signal that the plugin has stared up successfully const lifecycleService = await this.#getPluginLifecycleImpl(pluginId); await lifecycleService.startup(); @@ -299,6 +307,8 @@ export class BackendInitializer { const lifecycleService = await this.#getRootLifecycleImpl(); await lifecycleService.startup(); + initLogger.onAllStarted(); + // Once the backend is started, any uncaught errors or unhandled rejections are caught // and logged, in order to avoid crashing the entire backend on local failures. if (process.env.NODE_ENV !== 'test') { diff --git a/packages/backend-app-api/src/wiring/createInitializationLogger.ts b/packages/backend-app-api/src/wiring/createInitializationLogger.ts new file mode 100644 index 0000000000..566b5507ec --- /dev/null +++ b/packages/backend-app-api/src/wiring/createInitializationLogger.ts @@ -0,0 +1,79 @@ +/* + * 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 { RootLoggerService } from '@backstage/backend-plugin-api'; + +const LOGGER_INTERVAL_MAX = 60_000; + +function joinIds(ids: Iterable): string { + return [...ids].map(id => `'${id}'`).join(', '); +} + +export function createInitializationLogger( + pluginIds: string[], + rootLogger?: RootLoggerService, +): { + onPluginStarted(pluginId: string): void; + onAllStarted(): void; +} { + const logger = rootLogger?.child({ type: 'initialization' }); + const starting = new Set(pluginIds); + const started = new Set(); + + logger?.info(`Plugin initialization started: ${joinIds(pluginIds)}`); + + const getInitStatus = () => { + let status = ''; + if (started.size > 0) { + status = `, newly initialized: ${joinIds(started)}`; + started.clear(); + } + if (starting.size > 0) { + status += `, still initializing: ${joinIds(starting)}`; + } + return status; + }; + + // Periodically log the initialization status with a fibonacci backoff + let interval = 1000; + let prevInterval = 0; + let timeout: NodeJS.Timeout | undefined; + const onTimeout = () => { + logger?.info(`Plugin initialization in progress${getInitStatus()}`); + + const nextInterval = Math.min(interval + prevInterval, LOGGER_INTERVAL_MAX); + prevInterval = interval; + interval = nextInterval; + + timeout = setTimeout(onTimeout, nextInterval); + }; + timeout = setTimeout(onTimeout, interval); + + return { + onPluginStarted(pluginId: string) { + starting.delete(pluginId); + started.add(pluginId); + }, + onAllStarted() { + logger?.info(`Plugin initialization complete${getInitStatus()}`); + + if (timeout) { + clearTimeout(timeout); + timeout = undefined; + } + }, + }; +} From f1856032d38177963d2d1e15094fcd2d916d1b1d Mon Sep 17 00:00:00 2001 From: Alper Altay Date: Mon, 13 May 2024 14:36:30 +0200 Subject: [PATCH 395/567] fix: dynamic import of vite Signed-off-by: Alper Altay --- .changeset/tender-falcons-add.md | 5 +++++ packages/cli/src/lib/bundler/server.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/tender-falcons-add.md diff --git a/.changeset/tender-falcons-add.md b/.changeset/tender-falcons-add.md new file mode 100644 index 0000000000..60a947bcc3 --- /dev/null +++ b/.changeset/tender-falcons-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed the dynamic import of vite. diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 2b7311a34c..1099980aa7 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -165,7 +165,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be }); if (process.env.EXPERIMENTAL_VITE) { - const { default: vite } = await import('vite'); + const vite = import('vite'); const { default: viteReact } = await import('@vitejs/plugin-react'); const { nodePolyfills: viteNodePolyfills } = await import( 'vite-plugin-node-polyfills' From f8eeeb8774470ab1ac5558ae8eb18f60cd67e8ed Mon Sep 17 00:00:00 2001 From: Alper Altay Date: Mon, 13 May 2024 14:38:01 +0200 Subject: [PATCH 396/567] chore: added missing await Signed-off-by: Alper Altay --- packages/cli/src/lib/bundler/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 1099980aa7..53f3b2c25a 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -165,7 +165,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be }); if (process.env.EXPERIMENTAL_VITE) { - const vite = import('vite'); + const vite = await import('vite'); const { default: viteReact } = await import('@vitejs/plugin-react'); const { nodePolyfills: viteNodePolyfills } = await import( 'vite-plugin-node-polyfills' From 0972164fbc77997dddab4bad4cf666e2c31114f4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 May 2024 12:02:01 +0000 Subject: [PATCH 397/567] Version Packages --- .changeset/afraid-ghosts-watch.md | 5 - .changeset/afraid-needles-divide.md | 5 - .changeset/blue-hotels-shake.md | 7 - .changeset/brave-carrots-glow.md | 5 - .changeset/brave-planets-raise.md | 9 - .changeset/bright-pumpkins-rule.md | 5 - .changeset/chatty-cycles-unite.md | 9 - .changeset/chilly-adults-sing.md | 5 - .changeset/chilly-fireants-roll.md | 5 - .changeset/chilly-shoes-doubt.md | 5 - .changeset/clean-pants-camp.md | 5 - .changeset/cli-postpack-schmostschmack.md | 5 - .changeset/cold-cougars-float.md | 7 - .changeset/cold-rats-leave.md | 5 - .changeset/cool-elephants-march.md | 5 - .changeset/cool-garlics-clap.md | 5 - .changeset/create-app-1714476054.md | 5 - .changeset/create-app-1715088359.md | 5 - .changeset/cuddly-chairs-kick.md | 5 - .changeset/curly-shirts-flow.md | 5 - .changeset/curvy-planes-flash.md | 5 - .changeset/cyan-eagles-hammer.md | 6 - .changeset/cyan-suns-shave.md | 5 - .changeset/dirty-chairs-march.md | 5 - .changeset/dry-sloths-impress.md | 5 - .changeset/early-starfishes-hammer.md | 5 - .changeset/eighty-apricots-kneel.md | 5 - .changeset/eighty-bats-stare.md | 5 - .changeset/eleven-pandas-divide.md | 5 - .changeset/empty-beers-relax.md | 5 - .changeset/famous-crabs-laugh.md | 5 - .changeset/few-vans-cross.md | 5 - .changeset/five-cows-crash.md | 5 - .changeset/fix-stackoverflow.md | 5 - .changeset/flat-countries-clap.md | 5 - .changeset/fluffy-hotels-wait.md | 5 - .changeset/four-cooks-serve.md | 6 - .changeset/fresh-crews-impress.md | 5 - .changeset/funny-bees-taste.md | 5 - .changeset/funny-cars-jump.md | 24 - .changeset/fuzzy-seahorses-tell.md | 5 - .changeset/giant-donkeys-talk.md | 5 - .changeset/gold-waves-bake.md | 5 - .changeset/gorgeous-cameras-cross.md | 5 - .changeset/green-adults-push.md | 5 - .changeset/green-boxes-rescue.md | 5 - .changeset/grumpy-toes-tap.md | 5 - .changeset/happy-radios-kiss.md | 5 - .changeset/healthy-dots-ring.md | 6 - .changeset/healthy-shirts-roll.md | 5 - .changeset/heavy-trainers-fly.md | 5 - .changeset/hip-carrots-drive.md | 5 - .changeset/hot-forks-train.md | 5 - .changeset/itchy-gorillas-hope.md | 5 - .changeset/itchy-keys-wonder.md | 6 - .changeset/kind-toes-scream.md | 5 - .changeset/late-planes-fix.md | 6 - .changeset/lazy-phones-worry.md | 5 - .changeset/little-rockets-live.md | 7 - .changeset/loud-frogs-eat.md | 5 - .changeset/loud-timers-flow.md | 5 - .changeset/loud-vans-greet.md | 5 - .changeset/lovely-games-cry.md | 5 - .changeset/lucky-news-guess.md | 5 - .changeset/mean-ravens-dance.md | 7 - .changeset/metal-years-rhyme.md | 6 - .changeset/modern-radios-guess.md | 5 - .changeset/nasty-papayas-heal.md | 5 - .changeset/nervous-mayflies-float.md | 5 - .changeset/new-poets-promise.md | 5 - .changeset/nice-scissors-jog.md | 5 - .changeset/olive-pants-leave.md | 11 - .changeset/olive-rockets-drum.md | 5 - .changeset/orange-numbers-think.md | 5 - .changeset/perfect-beers-explode.md | 5 - .changeset/perfect-points-hope.md | 5 - .changeset/pink-nails-warn.md | 5 - .changeset/pink-years-peel.md | 6 - .changeset/pre.json | 395 ---- .changeset/proud-comics-love.md | 6 - .changeset/proud-doors-cheat.md | 5 - .changeset/purple-parents-sin.md | 5 - .changeset/purple-waves-smile.md | 5 - .changeset/quick-cats-argue.md | 7 - .changeset/quiet-boxes-build.md | 5 - .changeset/rare-fireants-tickle.md | 5 - .changeset/real-crabs-obey.md | 5 - .changeset/red-mangos-fly.md | 5 - .changeset/renovate-0d0bd5c.md | 11 - .changeset/renovate-228c530.md | 5 - .changeset/rich-adults-float.md | 5 - .changeset/selfish-pigs-glow.md | 6 - .changeset/selfish-walls-visit.md | 6 - .changeset/sharp-glasses-live.md | 5 - .changeset/shy-students-clap.md | 5 - .changeset/silent-wombats-hang.md | 5 - .changeset/six-scissors-smile.md | 5 - .changeset/sixty-bears-camp.md | 5 - .changeset/slimy-donkeys-laugh.md | 5 - .changeset/slimy-kids-behave.md | 5 - .changeset/smart-avocados-invent.md | 5 - .changeset/smooth-garlics-behave.md | 5 - .changeset/sour-socks-approve.md | 5 - .changeset/strange-rocks-study.md | 5 - .changeset/stupid-onions-know.md | 5 - .changeset/sweet-spiders-rhyme.md | 5 - .changeset/sweet-zoos-clap.md | 5 - .changeset/swift-humans-hunt.md | 5 - .changeset/tall-ads-shave.md | 5 - .changeset/tame-jars-double.md | 5 - .changeset/tame-jokes-bow.md | 5 - .changeset/tasty-apes-learn.md | 5 - .changeset/tasty-moles-jog.md | 5 - .changeset/tasty-rats-explain.md | 5 - .changeset/tender-falcons-add.md | 5 - .changeset/thick-llamas-itch.md | 5 - .changeset/thick-terms-rush.md | 5 - .changeset/thirty-mangos-travel.md | 5 - .changeset/tough-eggs-wink.md | 5 - .changeset/tricky-cougars-shout.md | 5 - .changeset/unlucky-days-play.md | 5 - .changeset/unlucky-rivers-collect.md | 5 - .changeset/warm-fans-promise.md | 5 - .changeset/wet-files-pretend.md | 5 - .changeset/wild-cats-hug.md | 7 - .changeset/wild-seahorses-grin.md | 6 - .changeset/young-guests-reflect.md | 5 - .changeset/young-olives-drop.md | 5 - docs/releases/v1.27.0-changelog.md | 1949 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 9 + packages/app-defaults/package.json | 2 +- packages/app-next-example-plugin/CHANGELOG.md | 8 + packages/app-next-example-plugin/package.json | 2 +- packages/app-next/CHANGELOG.md | 39 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 37 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 27 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 23 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 10 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 21 + .../package.json | 2 +- packages/backend-legacy/CHANGELOG.md | 40 + packages/backend-legacy/package.json | 2 +- packages/backend-openapi-utils/CHANGELOG.md | 7 + packages/backend-openapi-utils/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 10 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 9 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 13 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 36 + packages/backend/package.json | 2 +- packages/catalog-client/CHANGELOG.md | 7 + packages/catalog-client/package.json | 2 +- packages/catalog-model/CHANGELOG.md | 6 + packages/catalog-model/package.json | 2 +- packages/cli/CHANGELOG.md | 18 + packages/cli/package.json | 2 +- packages/core-app-api/CHANGELOG.md | 6 + packages/core-app-api/package.json | 2 +- packages/core-compat-api/CHANGELOG.md | 8 + packages/core-compat-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 14 + packages/core-components/package.json | 2 +- packages/create-app/CHANGELOG.md | 10 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 13 + packages/dev-utils/package.json | 2 +- packages/e2e-test/CHANGELOG.md | 7 + packages/e2e-test/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 6 + packages/eslint-plugin/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 14 + packages/frontend-app-api/package.json | 2 +- packages/frontend-plugin-api/CHANGELOG.md | 7 + packages/frontend-plugin-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 9 + packages/frontend-test-utils/package.json | 2 +- packages/integration-react/CHANGELOG.md | 7 + packages/integration-react/package.json | 2 +- packages/integration/CHANGELOG.md | 6 + packages/integration/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 14 + packages/repo-tools/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 17 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 10 + packages/techdocs-cli/package.json | 2 +- packages/test-utils/CHANGELOG.md | 8 + packages/test-utils/package.json | 2 +- packages/theme/CHANGELOG.md | 6 + packages/theme/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 15 + plugins/api-docs/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 11 + plugins/app-backend/package.json | 2 +- plugins/app-node/CHANGELOG.md | 7 + plugins/app-node/package.json | 2 +- plugins/app-visualizer/CHANGELOG.md | 8 + plugins/app-visualizer/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 29 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 11 + plugins/auth-node/package.json | 2 +- plugins/auth-react/CHANGELOG.md | 8 + plugins/auth-react/package.json | 2 +- plugins/bitbucket-cloud-common/CHANGELOG.md | 8 + plugins/bitbucket-cloud-common/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 14 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 12 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 12 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 23 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 12 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 28 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-common/CHANGELOG.md | 7 + plugins/catalog-common/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 13 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 19 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 16 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 16 + plugins/catalog-react/package.json | 2 +- .../catalog-unprocessed-entities/CHANGELOG.md | 8 + .../catalog-unprocessed-entities/package.json | 2 +- plugins/catalog/CHANGELOG.md | 23 + plugins/catalog/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 7 + plugins/config-schema/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 10 + plugins/devtools-backend/package.json | 2 +- plugins/devtools/CHANGELOG.md | 9 + plugins/devtools/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../events-backend-module-azure/CHANGELOG.md | 8 + .../events-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../events-backend-module-gerrit/CHANGELOG.md | 8 + .../events-backend-module-gerrit/package.json | 2 +- .../events-backend-module-github/CHANGELOG.md | 8 + .../events-backend-module-github/package.json | 2 +- .../events-backend-module-gitlab/CHANGELOG.md | 8 + .../events-backend-module-gitlab/package.json | 2 +- .../events-backend-test-utils/CHANGELOG.md | 7 + .../events-backend-test-utils/package.json | 2 +- plugins/events-backend/CHANGELOG.md | 9 + plugins/events-backend/package.json | 2 +- plugins/events-node/CHANGELOG.md | 8 + plugins/events-node/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 9 + .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 7 + plugins/example-todo-list/package.json | 2 +- plugins/home-react/CHANGELOG.md | 11 + plugins/home-react/package.json | 2 +- plugins/home/CHANGELOG.md | 20 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 15 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 11 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-common/CHANGELOG.md | 7 + plugins/kubernetes-common/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 9 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 10 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 11 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 19 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 12 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 19 + plugins/notifications/package.json | 2 +- plugins/org-react/CHANGELOG.md | 10 + plugins/org-react/package.json | 2 +- plugins/org/CHANGELOG.md | 13 + plugins/org/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 10 + plugins/permission-backend/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 9 + plugins/permission-node/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 8 + plugins/proxy-backend/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 31 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 8 + plugins/scaffolder-common/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 9 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 14 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 17 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 28 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 10 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 10 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 13 + plugins/search-backend/package.json | 2 +- plugins/search-react/CHANGELOG.md | 9 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 12 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 12 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 10 + plugins/signals-node/package.json | 2 +- plugins/signals/CHANGELOG.md | 8 + plugins/signals/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 15 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 15 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 11 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 8 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 19 + plugins/techdocs/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 10 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 13 + plugins/user-settings/package.json | 2 +- yarn.lock | 315 +-- 447 files changed, 4105 insertions(+), 1505 deletions(-) delete mode 100644 .changeset/afraid-ghosts-watch.md delete mode 100644 .changeset/afraid-needles-divide.md delete mode 100644 .changeset/blue-hotels-shake.md delete mode 100644 .changeset/brave-carrots-glow.md delete mode 100644 .changeset/brave-planets-raise.md delete mode 100644 .changeset/bright-pumpkins-rule.md delete mode 100644 .changeset/chatty-cycles-unite.md delete mode 100644 .changeset/chilly-adults-sing.md delete mode 100644 .changeset/chilly-fireants-roll.md delete mode 100644 .changeset/chilly-shoes-doubt.md delete mode 100644 .changeset/clean-pants-camp.md delete mode 100644 .changeset/cli-postpack-schmostschmack.md delete mode 100644 .changeset/cold-cougars-float.md delete mode 100644 .changeset/cold-rats-leave.md delete mode 100644 .changeset/cool-elephants-march.md delete mode 100644 .changeset/cool-garlics-clap.md delete mode 100644 .changeset/create-app-1714476054.md delete mode 100644 .changeset/create-app-1715088359.md delete mode 100644 .changeset/cuddly-chairs-kick.md delete mode 100644 .changeset/curly-shirts-flow.md delete mode 100644 .changeset/curvy-planes-flash.md delete mode 100644 .changeset/cyan-eagles-hammer.md delete mode 100644 .changeset/cyan-suns-shave.md delete mode 100644 .changeset/dirty-chairs-march.md delete mode 100644 .changeset/dry-sloths-impress.md delete mode 100644 .changeset/early-starfishes-hammer.md delete mode 100644 .changeset/eighty-apricots-kneel.md delete mode 100644 .changeset/eighty-bats-stare.md delete mode 100644 .changeset/eleven-pandas-divide.md delete mode 100644 .changeset/empty-beers-relax.md delete mode 100644 .changeset/famous-crabs-laugh.md delete mode 100644 .changeset/few-vans-cross.md delete mode 100644 .changeset/five-cows-crash.md delete mode 100644 .changeset/fix-stackoverflow.md delete mode 100644 .changeset/flat-countries-clap.md delete mode 100644 .changeset/fluffy-hotels-wait.md delete mode 100644 .changeset/four-cooks-serve.md delete mode 100644 .changeset/fresh-crews-impress.md delete mode 100644 .changeset/funny-bees-taste.md delete mode 100644 .changeset/funny-cars-jump.md delete mode 100644 .changeset/fuzzy-seahorses-tell.md delete mode 100644 .changeset/giant-donkeys-talk.md delete mode 100644 .changeset/gold-waves-bake.md delete mode 100644 .changeset/gorgeous-cameras-cross.md delete mode 100644 .changeset/green-adults-push.md delete mode 100644 .changeset/green-boxes-rescue.md delete mode 100644 .changeset/grumpy-toes-tap.md delete mode 100644 .changeset/happy-radios-kiss.md delete mode 100644 .changeset/healthy-dots-ring.md delete mode 100644 .changeset/healthy-shirts-roll.md delete mode 100644 .changeset/heavy-trainers-fly.md delete mode 100644 .changeset/hip-carrots-drive.md delete mode 100644 .changeset/hot-forks-train.md delete mode 100644 .changeset/itchy-gorillas-hope.md delete mode 100644 .changeset/itchy-keys-wonder.md delete mode 100644 .changeset/kind-toes-scream.md delete mode 100644 .changeset/late-planes-fix.md delete mode 100644 .changeset/lazy-phones-worry.md delete mode 100644 .changeset/little-rockets-live.md delete mode 100644 .changeset/loud-frogs-eat.md delete mode 100644 .changeset/loud-timers-flow.md delete mode 100644 .changeset/loud-vans-greet.md delete mode 100644 .changeset/lovely-games-cry.md delete mode 100644 .changeset/lucky-news-guess.md delete mode 100644 .changeset/mean-ravens-dance.md delete mode 100644 .changeset/metal-years-rhyme.md delete mode 100644 .changeset/modern-radios-guess.md delete mode 100644 .changeset/nasty-papayas-heal.md delete mode 100644 .changeset/nervous-mayflies-float.md delete mode 100644 .changeset/new-poets-promise.md delete mode 100644 .changeset/nice-scissors-jog.md delete mode 100644 .changeset/olive-pants-leave.md delete mode 100644 .changeset/olive-rockets-drum.md delete mode 100644 .changeset/orange-numbers-think.md delete mode 100644 .changeset/perfect-beers-explode.md delete mode 100644 .changeset/perfect-points-hope.md delete mode 100644 .changeset/pink-nails-warn.md delete mode 100644 .changeset/pink-years-peel.md delete mode 100644 .changeset/pre.json delete mode 100644 .changeset/proud-comics-love.md delete mode 100644 .changeset/proud-doors-cheat.md delete mode 100644 .changeset/purple-parents-sin.md delete mode 100644 .changeset/purple-waves-smile.md delete mode 100644 .changeset/quick-cats-argue.md delete mode 100644 .changeset/quiet-boxes-build.md delete mode 100644 .changeset/rare-fireants-tickle.md delete mode 100644 .changeset/real-crabs-obey.md delete mode 100644 .changeset/red-mangos-fly.md delete mode 100644 .changeset/renovate-0d0bd5c.md delete mode 100644 .changeset/renovate-228c530.md delete mode 100644 .changeset/rich-adults-float.md delete mode 100644 .changeset/selfish-pigs-glow.md delete mode 100644 .changeset/selfish-walls-visit.md delete mode 100644 .changeset/sharp-glasses-live.md delete mode 100644 .changeset/shy-students-clap.md delete mode 100644 .changeset/silent-wombats-hang.md delete mode 100644 .changeset/six-scissors-smile.md delete mode 100644 .changeset/sixty-bears-camp.md delete mode 100644 .changeset/slimy-donkeys-laugh.md delete mode 100644 .changeset/slimy-kids-behave.md delete mode 100644 .changeset/smart-avocados-invent.md delete mode 100644 .changeset/smooth-garlics-behave.md delete mode 100644 .changeset/sour-socks-approve.md delete mode 100644 .changeset/strange-rocks-study.md delete mode 100644 .changeset/stupid-onions-know.md delete mode 100644 .changeset/sweet-spiders-rhyme.md delete mode 100644 .changeset/sweet-zoos-clap.md delete mode 100644 .changeset/swift-humans-hunt.md delete mode 100644 .changeset/tall-ads-shave.md delete mode 100644 .changeset/tame-jars-double.md delete mode 100644 .changeset/tame-jokes-bow.md delete mode 100644 .changeset/tasty-apes-learn.md delete mode 100644 .changeset/tasty-moles-jog.md delete mode 100644 .changeset/tasty-rats-explain.md delete mode 100644 .changeset/tender-falcons-add.md delete mode 100644 .changeset/thick-llamas-itch.md delete mode 100644 .changeset/thick-terms-rush.md delete mode 100644 .changeset/thirty-mangos-travel.md delete mode 100644 .changeset/tough-eggs-wink.md delete mode 100644 .changeset/tricky-cougars-shout.md delete mode 100644 .changeset/unlucky-days-play.md delete mode 100644 .changeset/unlucky-rivers-collect.md delete mode 100644 .changeset/warm-fans-promise.md delete mode 100644 .changeset/wet-files-pretend.md delete mode 100644 .changeset/wild-cats-hug.md delete mode 100644 .changeset/wild-seahorses-grin.md delete mode 100644 .changeset/young-guests-reflect.md delete mode 100644 .changeset/young-olives-drop.md create mode 100644 docs/releases/v1.27.0-changelog.md diff --git a/.changeset/afraid-ghosts-watch.md b/.changeset/afraid-ghosts-watch.md deleted file mode 100644 index b277baaff1..0000000000 --- a/.changeset/afraid-ghosts-watch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-elasticsearch': patch ---- - -Fix never resolved indexer promise. diff --git a/.changeset/afraid-needles-divide.md b/.changeset/afraid-needles-divide.md deleted file mode 100644 index 6db7f449b0..0000000000 --- a/.changeset/afraid-needles-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Removed `packages/backend/src/types.ts` from the template as it is unused. It was mistakenly left in after moving the template to the new backend system. diff --git a/.changeset/blue-hotels-shake.md b/.changeset/blue-hotels-shake.md deleted file mode 100644 index 3d55aacffa..0000000000 --- a/.changeset/blue-hotels-shake.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-scaffolder-common': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-scaffolder': patch ---- - -Capturing more event clicks for scaffolder diff --git a/.changeset/brave-carrots-glow.md b/.changeset/brave-carrots-glow.md deleted file mode 100644 index b4493bf4f6..0000000000 --- a/.changeset/brave-carrots-glow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-node': minor ---- - -Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`. diff --git a/.changeset/brave-planets-raise.md b/.changeset/brave-planets-raise.md deleted file mode 100644 index 9aec85ae6e..0000000000 --- a/.changeset/brave-planets-raise.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-catalog': patch ---- - -Fix broken links in README. diff --git a/.changeset/bright-pumpkins-rule.md b/.changeset/bright-pumpkins-rule.md deleted file mode 100644 index 07de4f313c..0000000000 --- a/.changeset/bright-pumpkins-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Allow overriding `NotificationsPage` page properties diff --git a/.changeset/chatty-cycles-unite.md b/.changeset/chatty-cycles-unite.md deleted file mode 100644 index 93dffbb143..0000000000 --- a/.changeset/chatty-cycles-unite.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch -'@backstage/core-compat-api': patch -'@backstage/create-app': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch ---- - -Update local development dependencies. diff --git a/.changeset/chilly-adults-sing.md b/.changeset/chilly-adults-sing.md deleted file mode 100644 index 82f4cb88b2..0000000000 --- a/.changeset/chilly-adults-sing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Add lifecycle monitoring for the catalog processing diff --git a/.changeset/chilly-fireants-roll.md b/.changeset/chilly-fireants-roll.md deleted file mode 100644 index 93c05fecb7..0000000000 --- a/.changeset/chilly-fireants-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-home': patch ---- - -Use relative time when displaying visits from the same day diff --git a/.changeset/chilly-shoes-doubt.md b/.changeset/chilly-shoes-doubt.md deleted file mode 100644 index 978600f5b4..0000000000 --- a/.changeset/chilly-shoes-doubt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Fixed disabling of user photo fetching. Previously, the config value wasn't propagated properly, so user photos was still being fetched despite disabled by config. diff --git a/.changeset/clean-pants-camp.md b/.changeset/clean-pants-camp.md deleted file mode 100644 index 2070305a6d..0000000000 --- a/.changeset/clean-pants-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-ldap': patch ---- - -Remove dependency to Winston Logger and use Backstage LoggerService instead diff --git a/.changeset/cli-postpack-schmostschmack.md b/.changeset/cli-postpack-schmostschmack.md deleted file mode 100644 index 2500c89a8c..0000000000 --- a/.changeset/cli-postpack-schmostschmack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The `build-workspace` command no longer manually runs `yarn postpack`, relying instead on the fact that running `yarn pack` will automatically invoke the `postpack` script. No action is necessary if you are running the latest version of yarn 1, 3, or 4. diff --git a/.changeset/cold-cougars-float.md b/.changeset/cold-cougars-float.md deleted file mode 100644 index 1ddc78a34b..0000000000 --- a/.changeset/cold-cougars-float.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-unprocessed': patch -'@backstage/backend-dynamic-feature-service': patch -'@backstage/plugin-search-backend-module-catalog': patch ---- - -Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. diff --git a/.changeset/cold-rats-leave.md b/.changeset/cold-rats-leave.md deleted file mode 100644 index 6fe28d6371..0000000000 --- a/.changeset/cold-rats-leave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix issue with the log format not being respected when logging from actions diff --git a/.changeset/cool-elephants-march.md b/.changeset/cool-elephants-march.md deleted file mode 100644 index 2396f9553d..0000000000 --- a/.changeset/cool-elephants-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitea': patch ---- - -Allow defining `repoVisibility` field for the action `publish:gitea` diff --git a/.changeset/cool-garlics-clap.md b/.changeset/cool-garlics-clap.md deleted file mode 100644 index 223d0a1233..0000000000 --- a/.changeset/cool-garlics-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Properly log the `errorInfo` in `ErrorBoundary` diff --git a/.changeset/create-app-1714476054.md b/.changeset/create-app-1714476054.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1714476054.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/create-app-1715088359.md b/.changeset/create-app-1715088359.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1715088359.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/cuddly-chairs-kick.md b/.changeset/cuddly-chairs-kick.md deleted file mode 100644 index ebb53b8e26..0000000000 --- a/.changeset/cuddly-chairs-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Add ability to configure the Node.js HTTP Server when configuring the root HTTP Router service diff --git a/.changeset/curly-shirts-flow.md b/.changeset/curly-shirts-flow.md deleted file mode 100644 index b9a73809ba..0000000000 --- a/.changeset/curly-shirts-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/repo-tools': patch ---- - -Update the paths logic in the api reports command to support complex subpaths diff --git a/.changeset/curvy-planes-flash.md b/.changeset/curvy-planes-flash.md deleted file mode 100644 index 0d3368e53d..0000000000 --- a/.changeset/curvy-planes-flash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-bitbucket-server': patch ---- - -Allow skipping archived repositories (`skipArchivedRepos` flag) on Bitbucket. diff --git a/.changeset/cyan-eagles-hammer.md b/.changeset/cyan-eagles-hammer.md deleted file mode 100644 index dfe6f0bc62..0000000000 --- a/.changeset/cyan-eagles-hammer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch -'@backstage/plugin-notifications-backend': patch ---- - -Fixed email processor `esm` issue and config reading diff --git a/.changeset/cyan-suns-shave.md b/.changeset/cyan-suns-shave.md deleted file mode 100644 index 09e51d9548..0000000000 --- a/.changeset/cyan-suns-shave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-unprocessed': patch ---- - -Correctly convert owner to string in case owner has not been provided diff --git a/.changeset/dirty-chairs-march.md b/.changeset/dirty-chairs-march.md deleted file mode 100644 index c4e22657a6..0000000000 --- a/.changeset/dirty-chairs-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Add merge method and squash option for project creation diff --git a/.changeset/dry-sloths-impress.md b/.changeset/dry-sloths-impress.md deleted file mode 100644 index 6bd5ba4156..0000000000 --- a/.changeset/dry-sloths-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': minor ---- - -Introduce a domain attribute to the domain entity to allow a hierarchy of domains to exist. diff --git a/.changeset/early-starfishes-hammer.md b/.changeset/early-starfishes-hammer.md deleted file mode 100644 index abe17e85a2..0000000000 --- a/.changeset/early-starfishes-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`. diff --git a/.changeset/eighty-apricots-kneel.md b/.changeset/eighty-apricots-kneel.md deleted file mode 100644 index a9cabaf378..0000000000 --- a/.changeset/eighty-apricots-kneel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@techdocs/cli': patch ---- - -Fix cookie endpoint mock for `serve` diff --git a/.changeset/eighty-bats-stare.md b/.changeset/eighty-bats-stare.md deleted file mode 100644 index 6ebb2e9265..0000000000 --- a/.changeset/eighty-bats-stare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Update path in Readme for Plugin Techdocs to show the correct setup information. diff --git a/.changeset/eleven-pandas-divide.md b/.changeset/eleven-pandas-divide.md deleted file mode 100644 index 912d34570a..0000000000 --- a/.changeset/eleven-pandas-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-gitlab': patch ---- - -Added events support for `GitlabDiscoveryEntityProvider` and `GitlabOrgDiscoveryEntityProvider`. diff --git a/.changeset/empty-beers-relax.md b/.changeset/empty-beers-relax.md deleted file mode 100644 index bbfcef0933..0000000000 --- a/.changeset/empty-beers-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added `HarnessURLReader` with `readUrl` support. diff --git a/.changeset/famous-crabs-laugh.md b/.changeset/famous-crabs-laugh.md deleted file mode 100644 index 282cbb9f93..0000000000 --- a/.changeset/famous-crabs-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Preparing for a stable new backend system release, we are deprecating utilities in the `backend-common` that are not used by the core framework, such as the isomorphic `Git` class. As we will no longer support the isomorphic `Git` utility in the framework packages, we recommend plugins that start maintaining their own implementation of this class. diff --git a/.changeset/few-vans-cross.md b/.changeset/few-vans-cross.md deleted file mode 100644 index d7a12f6c10..0000000000 --- a/.changeset/few-vans-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Plugins created through the `legacyPlugin` helper are now able to authenticate requests from plugins that are fully implemented using the new backend system. This fixes the `Key for the ES256 algorithm must be one of type KeyObject or CryptoKey. Received an instance of Uint8Array` error. diff --git a/.changeset/five-cows-crash.md b/.changeset/five-cows-crash.md deleted file mode 100644 index 2c298dbad3..0000000000 --- a/.changeset/five-cows-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': minor ---- - -`EntityListComponent` uses `entityPresentationApi` instead of `humanizeEntityRef` to display Entity diff --git a/.changeset/fix-stackoverflow.md b/.changeset/fix-stackoverflow.md deleted file mode 100644 index 6ce9f15737..0000000000 --- a/.changeset/fix-stackoverflow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch ---- - -Fix plugin/incremental-ingestion 'Maximum call stack size exceeded' error when ingest large entities. diff --git a/.changeset/flat-countries-clap.md b/.changeset/flat-countries-clap.md deleted file mode 100644 index 9c962b04a3..0000000000 --- a/.changeset/flat-countries-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/repo-tools': minor ---- - -Adds 2 new commands `repo schema openapi diff` and `package schema openapi diff`. `repo schema openapi diff` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes. They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. diff --git a/.changeset/fluffy-hotels-wait.md b/.changeset/fluffy-hotels-wait.md deleted file mode 100644 index a578f2fb4a..0000000000 --- a/.changeset/fluffy-hotels-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Both the target and types library have been bumped from ES2021 to ES2022 in `@backstage/cli/config/tsconfig.json`. diff --git a/.changeset/four-cooks-serve.md b/.changeset/four-cooks-serve.md deleted file mode 100644 index 864807cc49..0000000000 --- a/.changeset/four-cooks-serve.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': patch -'@backstage/plugin-notifications-node': patch ---- - -Support for filtering entities from notification recipients after resolving them from the recipients diff --git a/.changeset/fresh-crews-impress.md b/.changeset/fresh-crews-impress.md deleted file mode 100644 index 18f98f1dd9..0000000000 --- a/.changeset/fresh-crews-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Move the JWKS registration outside of the lifecycle middleware diff --git a/.changeset/funny-bees-taste.md b/.changeset/funny-bees-taste.md deleted file mode 100644 index a9b2d84997..0000000000 --- a/.changeset/funny-bees-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -The user can newly mark all unread messages as read at one click. diff --git a/.changeset/funny-cars-jump.md b/.changeset/funny-cars-jump.md deleted file mode 100644 index c67204d68c..0000000000 --- a/.changeset/funny-cars-jump.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch -'@backstage/backend-dynamic-feature-service': patch -'@backstage/plugin-scaffolder-backend-module-github': patch -'@backstage/plugin-scaffolder-backend-module-gitlab': patch -'@backstage/plugin-search-backend-module-pg': patch -'@backstage/plugin-notifications-backend': patch -'@backstage/plugin-user-settings-backend': patch -'@backstage/backend-plugin-api': patch -'@backstage/backend-test-utils': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/backend-app-api': patch -'@backstage/backend-common': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-scaffolder-node': patch -'@backstage/backend-tasks': patch -'@backstage/plugin-techdocs-node': patch -'@backstage/plugin-auth-backend': patch -'@backstage/repo-tools': patch -'@backstage/plugin-app-backend': patch ---- - -Move path utilities from `backend-common` to the `backend-plugin-api` package. diff --git a/.changeset/fuzzy-seahorses-tell.md b/.changeset/fuzzy-seahorses-tell.md deleted file mode 100644 index f184e9ce95..0000000000 --- a/.changeset/fuzzy-seahorses-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Fixed an internal circular import that broke Jest mocks. diff --git a/.changeset/giant-donkeys-talk.md b/.changeset/giant-donkeys-talk.md deleted file mode 100644 index 62af2eb7e8..0000000000 --- a/.changeset/giant-donkeys-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Support relative links in notifications sent via email diff --git a/.changeset/gold-waves-bake.md b/.changeset/gold-waves-bake.md deleted file mode 100644 index 2883b7b967..0000000000 --- a/.changeset/gold-waves-bake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add support for `versions:migrate` to do code changes. Can be skipped with `--no-code-changes` diff --git a/.changeset/gorgeous-cameras-cross.md b/.changeset/gorgeous-cameras-cross.md deleted file mode 100644 index 50d7ddb2d7..0000000000 --- a/.changeset/gorgeous-cameras-cross.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-addons-test-utils': patch ---- - -Fix bug in TechDocsAddonTester when jest.resetAllMocks is called between tests diff --git a/.changeset/green-adults-push.md b/.changeset/green-adults-push.md deleted file mode 100644 index 3edc21c385..0000000000 --- a/.changeset/green-adults-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add preserveModules to rollup, which allows better async loading and tree-shaking in webpack diff --git a/.changeset/green-boxes-rescue.md b/.changeset/green-boxes-rescue.md deleted file mode 100644 index 146695a320..0000000000 --- a/.changeset/green-boxes-rescue.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': patch ---- - -Allow sending notifications by email with the new notifications module diff --git a/.changeset/grumpy-toes-tap.md b/.changeset/grumpy-toes-tap.md deleted file mode 100644 index ff3fbbf2df..0000000000 --- a/.changeset/grumpy-toes-tap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-devtools-backend': patch ---- - -Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance diff --git a/.changeset/happy-radios-kiss.md b/.changeset/happy-radios-kiss.md deleted file mode 100644 index 1097c832d8..0000000000 --- a/.changeset/happy-radios-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API. diff --git a/.changeset/healthy-dots-ring.md b/.changeset/healthy-dots-ring.md deleted file mode 100644 index 005e7e6616..0000000000 --- a/.changeset/healthy-dots-ring.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-scaffolder-node': patch ---- - -Scaffolder workspace serialization diff --git a/.changeset/healthy-shirts-roll.md b/.changeset/healthy-shirts-roll.md deleted file mode 100644 index aec74a1181..0000000000 --- a/.changeset/healthy-shirts-roll.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-signals-backend': patch ---- - -Improved signal lifecycle management and added server side pinging of connections diff --git a/.changeset/heavy-trainers-fly.md b/.changeset/heavy-trainers-fly.md deleted file mode 100644 index 48560b7ddd..0000000000 --- a/.changeset/heavy-trainers-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added config prop `ensureSchemaExists` to support postgres instances where user can create schemas but not databases. diff --git a/.changeset/hip-carrots-drive.md b/.changeset/hip-carrots-drive.md deleted file mode 100644 index 5852916b74..0000000000 --- a/.changeset/hip-carrots-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests. diff --git a/.changeset/hot-forks-train.md b/.changeset/hot-forks-train.md deleted file mode 100644 index a5300085b6..0000000000 --- a/.changeset/hot-forks-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Allow showing notifications as snackbars in the UI diff --git a/.changeset/itchy-gorillas-hope.md b/.changeset/itchy-gorillas-hope.md deleted file mode 100644 index cd285161ff..0000000000 --- a/.changeset/itchy-gorillas-hope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Removed the Tech Radar and GitHub Actions plugins from the template, which have been moved to the community plugins repository. diff --git a/.changeset/itchy-keys-wonder.md b/.changeset/itchy-keys-wonder.md deleted file mode 100644 index 879dc096a4..0000000000 --- a/.changeset/itchy-keys-wonder.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch -'@backstage/backend-app-api': patch ---- - -Redact `meta` fields too with the logger diff --git a/.changeset/kind-toes-scream.md b/.changeset/kind-toes-scream.md deleted file mode 100644 index d634f17ce4..0000000000 --- a/.changeset/kind-toes-scream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The `versions:bump` command will no longer exit with a non-zero status if the version bump fails due to forbidden duplicate package installations. It will now also provide more information about how to troubleshoot such an error. The set of forbidden duplicates has also been expanded to include all `@backstage/*-app-api` packages. diff --git a/.changeset/late-planes-fix.md b/.changeset/late-planes-fix.md deleted file mode 100644 index a57d7ec1d4..0000000000 --- a/.changeset/late-planes-fix.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-search-backend': patch ---- - -Add lifecycle monitoring for the search index registry diff --git a/.changeset/lazy-phones-worry.md b/.changeset/lazy-phones-worry.md deleted file mode 100644 index 2c64f81f2c..0000000000 --- a/.changeset/lazy-phones-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-app-api': minor ---- - -Extensions in app-config now always affect ordering. Previously, only when enabling disabled extensions did they rise to the top. diff --git a/.changeset/little-rockets-live.md b/.changeset/little-rockets-live.md deleted file mode 100644 index 767fda917e..0000000000 --- a/.changeset/little-rockets-live.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-aws-alb-provider': patch -'@backstage/plugin-auth-backend-module-github-provider': patch -'@backstage/plugin-auth-backend': patch ---- - -fix: Move config declarations to appropriate auth backend modules diff --git a/.changeset/loud-frogs-eat.md b/.changeset/loud-frogs-eat.md deleted file mode 100644 index dd1ab978f1..0000000000 --- a/.changeset/loud-frogs-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': patch ---- - -Add examples for `gitlab:repo:push` scaffolder action & improve related tests diff --git a/.changeset/loud-timers-flow.md b/.changeset/loud-timers-flow.md deleted file mode 100644 index d7d0d96970..0000000000 --- a/.changeset/loud-timers-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Remove explicit `alg` check for user tokens in `verifyToken` diff --git a/.changeset/loud-vans-greet.md b/.changeset/loud-vans-greet.md deleted file mode 100644 index 732413174b..0000000000 --- a/.changeset/loud-vans-greet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Internal refactor to only create one external token handler diff --git a/.changeset/lovely-games-cry.md b/.changeset/lovely-games-cry.md deleted file mode 100644 index 5209b93496..0000000000 --- a/.changeset/lovely-games-cry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-react': patch ---- - -When using `CookieAuthRefreshProvider` or `useCookieAuthRefresh`, a 404 response from the cookie endpoint will now be treated as if cookie auth is disabled and is not needed. diff --git a/.changeset/lucky-news-guess.md b/.changeset/lucky-news-guess.md deleted file mode 100644 index 9c2cc4e8b0..0000000000 --- a/.changeset/lucky-news-guess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch ---- - -Add examples for `publish:bitbucketServer:pull-request` scaffolder action & improve related tests diff --git a/.changeset/mean-ravens-dance.md b/.changeset/mean-ravens-dance.md deleted file mode 100644 index 061e12cd9e..0000000000 --- a/.changeset/mean-ravens-dance.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-common': minor ---- - -Internal refactor of the database code. - -**BREAKING**: The helper functions `createDatabaseClient` and `ensureDatabaseExists` have been removed from the public interface, since they have no usage within the repository and never were suitable for calling from the outside. Please consider using `coreServices.database` or `DatabaseManager` directly wherever possible instead. diff --git a/.changeset/metal-years-rhyme.md b/.changeset/metal-years-rhyme.md deleted file mode 100644 index 9c6daebdac..0000000000 --- a/.changeset/metal-years-rhyme.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-techdocs': patch ---- - -The `techdocs.builder` config is now optional and it will default to `local`. diff --git a/.changeset/modern-radios-guess.md b/.changeset/modern-radios-guess.md deleted file mode 100644 index 8b68ebcdf4..0000000000 --- a/.changeset/modern-radios-guess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/eslint-plugin': patch ---- - -add some `pickers` fixes diff --git a/.changeset/nasty-papayas-heal.md b/.changeset/nasty-papayas-heal.md deleted file mode 100644 index 0e8e3f7ccc..0000000000 --- a/.changeset/nasty-papayas-heal.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-github': patch ---- - -Adding support to change the default commit author for `publish:github:pull-request` diff --git a/.changeset/nervous-mayflies-float.md b/.changeset/nervous-mayflies-float.md deleted file mode 100644 index e8d29be280..0000000000 --- a/.changeset/nervous-mayflies-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Only create a single actual connection to memcache/redis, even in cases where many `CacheService` instances are made diff --git a/.changeset/new-poets-promise.md b/.changeset/new-poets-promise.md deleted file mode 100644 index 206bf3e0d8..0000000000 --- a/.changeset/new-poets-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-notifications': patch ---- - -Add a new scaffolder action to allow sending notifications from templates diff --git a/.changeset/nice-scissors-jog.md b/.changeset/nice-scissors-jog.md deleted file mode 100644 index 7065a9e63f..0000000000 --- a/.changeset/nice-scissors-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Show all notifications by default to match the sidebar item status diff --git a/.changeset/olive-pants-leave.md b/.changeset/olive-pants-leave.md deleted file mode 100644 index e14cc69912..0000000000 --- a/.changeset/olive-pants-leave.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added support for camel case CSP directives in app-config. For example: - -```yaml -backend: - csp: - upgradeInsecureRequests: false -``` diff --git a/.changeset/olive-rockets-drum.md b/.changeset/olive-rockets-drum.md deleted file mode 100644 index e28ba6101a..0000000000 --- a/.changeset/olive-rockets-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -`MultiEntityPicker` uses `EntityDisplayName` instead of `humanizeEntityRef` to display entity. diff --git a/.changeset/orange-numbers-think.md b/.changeset/orange-numbers-think.md deleted file mode 100644 index 21dc7ce76b..0000000000 --- a/.changeset/orange-numbers-think.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-catalog': patch ---- - -Fix wiring of the module exported at the `/alpha` path, which was causing authentication failures. diff --git a/.changeset/perfect-beers-explode.md b/.changeset/perfect-beers-explode.md deleted file mode 100644 index 3045ca7f12..0000000000 --- a/.changeset/perfect-beers-explode.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Increase default and allow modifying notification snackbar auto hide duration diff --git a/.changeset/perfect-points-hope.md b/.changeset/perfect-points-hope.md deleted file mode 100644 index cfc42f77a3..0000000000 --- a/.changeset/perfect-points-hope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-oidc-provider': patch ---- - -Add nonce to authorize request to be added in ID token diff --git a/.changeset/pink-nails-warn.md b/.changeset/pink-nails-warn.md deleted file mode 100644 index 6bdbf465b0..0000000000 --- a/.changeset/pink-nails-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added logging of all plugins being initialized, periodic status, and completion. diff --git a/.changeset/pink-years-peel.md b/.changeset/pink-years-peel.md deleted file mode 100644 index 339889f27e..0000000000 --- a/.changeset/pink-years-peel.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -GitHub push events now schedule a refresh on entities that have a `refresh_key` matching the `catalogPath` config itself. -This allows to support a `catalogPath` configuration that uses glob patterns. diff --git a/.changeset/pre.json b/.changeset/pre.json deleted file mode 100644 index 8c72a9d481..0000000000 --- a/.changeset/pre.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "mode": "exit", - "tag": "next", - "initialVersions": { - "example-app": "0.2.96", - "@backstage/app-defaults": "1.5.4", - "example-app-next": "0.0.10", - "app-next-example-plugin": "0.0.10", - "example-backend": "0.2.97", - "@backstage/backend-app-api": "0.7.0", - "@backstage/backend-common": "0.21.7", - "@backstage/backend-defaults": "0.2.17", - "@backstage/backend-dev-utils": "0.1.4", - "@backstage/backend-dynamic-feature-service": "0.2.9", - "example-backend-next": "0.0.25", - "@backstage/backend-openapi-utils": "0.1.10", - "@backstage/backend-plugin-api": "0.6.17", - "@backstage/backend-tasks": "0.5.22", - "@backstage/backend-test-utils": "0.3.7", - "@backstage/catalog-client": "1.6.4", - "@backstage/catalog-model": "1.4.5", - "@backstage/cli": "0.26.3", - "@backstage/cli-common": "0.1.13", - "@backstage/cli-node": "0.2.5", - "@backstage/codemods": "0.1.48", - "@backstage/config": "1.2.0", - "@backstage/config-loader": "1.8.0", - "@backstage/core-app-api": "1.12.4", - "@backstage/core-compat-api": "0.2.4", - "@backstage/core-components": "0.14.4", - "@backstage/core-plugin-api": "1.9.2", - "@backstage/create-app": "0.5.14", - "@backstage/dev-utils": "1.0.31", - "e2e-test": "0.2.15", - "@backstage/e2e-test-utils": "0.1.1", - "@backstage/errors": "1.2.4", - "@backstage/eslint-plugin": "0.1.7", - "@backstage/frontend-app-api": "0.6.4", - "@backstage/frontend-plugin-api": "0.6.4", - "@backstage/frontend-test-utils": "0.1.6", - "@backstage/integration": "1.10.0", - "@backstage/integration-aws-node": "0.1.12", - "@backstage/integration-react": "1.1.26", - "@backstage/release-manifests": "0.0.11", - "@backstage/repo-tools": "0.8.0", - "@techdocs/cli": "1.8.9", - "techdocs-cli-embedded-app": "0.2.95", - "@backstage/test-utils": "1.5.4", - "@backstage/theme": "0.5.3", - "@backstage/types": "1.1.1", - "@backstage/version-bridge": "1.0.8", - "@backstage/plugin-adr": "0.6.17", - "@backstage/plugin-adr-backend": "0.4.14", - "@backstage/plugin-adr-common": "0.2.22", - "@backstage/plugin-airbrake": "0.3.34", - "@backstage/plugin-airbrake-backend": "0.3.14", - "@backstage/plugin-allure": "0.1.50", - "@backstage/plugin-analytics-module-ga": "0.2.4", - "@backstage/plugin-analytics-module-ga4": "0.2.4", - "@backstage/plugin-analytics-module-newrelic-browser": "0.1.4", - "@backstage/plugin-apache-airflow": "0.2.24", - "@backstage/plugin-api-docs": "0.11.4", - "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.6", - "@backstage/plugin-apollo-explorer": "0.2.0", - "@backstage/plugin-app-backend": "0.3.65", - "@backstage/plugin-app-node": "0.1.17", - "@backstage/plugin-app-visualizer": "0.1.5", - "@backstage/plugin-auth-backend": "0.22.4", - "@backstage/plugin-auth-backend-module-atlassian-provider": "0.1.9", - "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.1.9", - "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.1.0", - "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.1.0", - "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.1.0", - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.2.12", - "@backstage/plugin-auth-backend-module-github-provider": "0.1.14", - "@backstage/plugin-auth-backend-module-gitlab-provider": "0.1.14", - "@backstage/plugin-auth-backend-module-google-provider": "0.1.14", - "@backstage/plugin-auth-backend-module-guest-provider": "0.1.3", - "@backstage/plugin-auth-backend-module-microsoft-provider": "0.1.12", - "@backstage/plugin-auth-backend-module-oauth2-provider": "0.1.14", - "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.1.10", - "@backstage/plugin-auth-backend-module-oidc-provider": "0.1.8", - "@backstage/plugin-auth-backend-module-okta-provider": "0.0.10", - "@backstage/plugin-auth-backend-module-pinniped-provider": "0.1.11", - "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.1.9", - "@backstage/plugin-auth-node": "0.4.12", - "@backstage/plugin-auth-react": "0.1.0", - "@backstage/plugin-azure-devops": "0.4.3", - "@backstage/plugin-azure-devops-backend": "0.6.4", - "@backstage/plugin-azure-devops-common": "0.4.1", - "@backstage/plugin-azure-sites": "0.1.23", - "@backstage/plugin-azure-sites-backend": "0.3.4", - "@backstage/plugin-azure-sites-common": "0.1.3", - "@backstage/plugin-badges": "0.2.58", - "@backstage/plugin-badges-backend": "0.4.0", - "@backstage/plugin-bazaar": "0.2.26", - "@backstage/plugin-bazaar-backend": "0.3.15", - "@backstage/plugin-bitbucket-cloud-common": "0.2.18", - "@backstage/plugin-bitrise": "0.1.61", - "@backstage/plugin-catalog": "1.19.0", - "@backstage/plugin-catalog-backend": "1.21.1", - "@backstage/plugin-catalog-backend-module-aws": "0.3.12", - "@backstage/plugin-catalog-backend-module-azure": "0.1.37", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.2.0", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.2.4", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.31", - "@backstage/plugin-catalog-backend-module-gcp": "0.1.18", - "@backstage/plugin-catalog-backend-module-gerrit": "0.1.34", - "@backstage/plugin-catalog-backend-module-github": "0.6.0", - "@backstage/plugin-catalog-backend-module-github-org": "0.1.12", - "@backstage/plugin-catalog-backend-module-gitlab": "0.3.15", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.4.22", - "@backstage/plugin-catalog-backend-module-ldap": "0.5.33", - "@backstage/plugin-catalog-backend-module-msgraph": "0.5.25", - "@backstage/plugin-catalog-backend-module-openapi": "0.1.35", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.1.23", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.15", - "@backstage/plugin-catalog-backend-module-unprocessed": "0.4.4", - "@backstage/plugin-catalog-common": "1.0.22", - "@backstage/plugin-catalog-graph": "0.4.4", - "@backstage/plugin-catalog-import": "0.10.10", - "@backstage/plugin-catalog-node": "1.11.1", - "@backstage/plugin-catalog-react": "1.11.3", - "@backstage/plugin-catalog-unprocessed-entities": "0.2.3", - "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.1", - "@backstage/plugin-cicd-statistics": "0.1.36", - "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.30", - "@backstage/plugin-circleci": "0.3.34", - "@backstage/plugin-cloudbuild": "0.5.1", - "@backstage/plugin-code-climate": "0.1.34", - "@backstage/plugin-code-coverage": "0.2.27", - "@backstage/plugin-code-coverage-backend": "0.2.31", - "@backstage/plugin-codescene": "0.1.26", - "@backstage/plugin-config-schema": "0.1.54", - "@backstage/plugin-cost-insights": "0.12.23", - "@backstage/plugin-cost-insights-common": "0.1.2", - "@backstage/plugin-devtools": "0.1.13", - "@backstage/plugin-devtools-backend": "0.3.3", - "@backstage/plugin-devtools-common": "0.1.9", - "@backstage/plugin-dynatrace": "10.0.3", - "@backstage/plugin-entity-feedback": "0.2.17", - "@backstage/plugin-entity-feedback-backend": "0.2.14", - "@backstage/plugin-entity-feedback-common": "0.1.3", - "@backstage/plugin-entity-validation": "0.1.19", - "@backstage/plugin-events-backend": "0.3.4", - "@backstage/plugin-events-backend-module-aws-sqs": "0.3.3", - "@backstage/plugin-events-backend-module-azure": "0.2.3", - "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.3", - "@backstage/plugin-events-backend-module-gerrit": "0.2.3", - "@backstage/plugin-events-backend-module-github": "0.2.3", - "@backstage/plugin-events-backend-module-gitlab": "0.2.3", - "@backstage/plugin-events-backend-test-utils": "0.1.27", - "@backstage/plugin-events-node": "0.3.3", - "@internal/plugin-todo-list": "1.0.26", - "@internal/plugin-todo-list-backend": "1.0.26", - "@internal/plugin-todo-list-common": "1.0.18", - "@backstage/plugin-explore": "0.4.20", - "@backstage/plugin-explore-backend": "0.0.27", - "@backstage/plugin-explore-common": "0.0.2", - "@backstage/plugin-explore-react": "0.0.38", - "@backstage/plugin-firehydrant": "0.2.18", - "@backstage/plugin-fossa": "0.2.66", - "@backstage/plugin-gcalendar": "0.3.27", - "@backstage/plugin-gcp-projects": "0.3.50", - "@backstage/plugin-git-release-manager": "0.3.46", - "@backstage/plugin-github-actions": "0.6.15", - "@backstage/plugin-github-deployments": "0.1.65", - "@backstage/plugin-github-issues": "0.4.1", - "@backstage/plugin-github-pull-requests-board": "0.2.0", - "@backstage/plugin-gitops-profiles": "0.3.49", - "@backstage/plugin-gocd": "0.1.40", - "@backstage/plugin-graphiql": "0.3.7", - "@backstage/plugin-graphql-voyager": "0.1.16", - "@backstage/plugin-home": "0.7.3", - "@backstage/plugin-home-react": "0.1.12", - "@backstage/plugin-ilert": "0.2.23", - "@backstage/plugin-jenkins": "0.9.9", - "@backstage/plugin-jenkins-backend": "0.4.4", - "@backstage/plugin-jenkins-common": "0.1.25", - "@backstage/plugin-kafka": "0.3.34", - "@backstage/plugin-kafka-backend": "0.3.15", - "@backstage/plugin-kubernetes": "0.11.9", - "@backstage/plugin-kubernetes-backend": "0.17.0", - "@backstage/plugin-kubernetes-cluster": "0.0.10", - "@backstage/plugin-kubernetes-common": "0.7.5", - "@backstage/plugin-kubernetes-node": "0.1.11", - "@backstage/plugin-kubernetes-react": "0.3.4", - "@backstage/plugin-lighthouse": "0.4.19", - "@backstage/plugin-lighthouse-backend": "0.4.10", - "@backstage/plugin-lighthouse-common": "0.1.5", - "@backstage/plugin-linguist": "0.1.19", - "@backstage/plugin-linguist-backend": "0.5.15", - "@backstage/plugin-linguist-common": "0.1.2", - "@backstage/plugin-microsoft-calendar": "0.1.16", - "@backstage/plugin-newrelic": "0.3.49", - "@backstage/plugin-newrelic-dashboard": "0.3.9", - "@backstage/plugin-nomad": "0.1.15", - "@backstage/plugin-nomad-backend": "0.1.19", - "@backstage/plugin-notifications": "0.2.0", - "@backstage/plugin-notifications-backend": "0.2.0", - "@backstage/plugin-notifications-common": "0.0.3", - "@backstage/plugin-notifications-node": "0.1.3", - "@backstage/plugin-octopus-deploy": "0.2.16", - "@backstage/plugin-opencost": "0.2.9", - "@backstage/plugin-org": "0.6.24", - "@backstage/plugin-org-react": "0.1.23", - "@backstage/plugin-pagerduty": "0.7.6", - "@backstage/plugin-periskop": "0.1.32", - "@backstage/plugin-periskop-backend": "0.2.15", - "@backstage/plugin-permission-backend": "0.5.41", - "@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.14", - "@backstage/plugin-permission-common": "0.7.13", - "@backstage/plugin-permission-node": "0.7.28", - "@backstage/plugin-permission-react": "0.4.22", - "@backstage/plugin-playlist": "0.2.8", - "@backstage/plugin-playlist-backend": "0.3.21", - "@backstage/plugin-playlist-common": "0.1.15", - "@backstage/plugin-proxy-backend": "0.4.15", - "@backstage/plugin-puppetdb": "0.1.17", - "@backstage/plugin-rollbar": "0.4.34", - "@backstage/plugin-rollbar-backend": "0.1.62", - "@backstage/plugin-scaffolder": "1.19.3", - "@backstage/plugin-scaffolder-backend": "1.22.4", - "@backstage/plugin-scaffolder-backend-module-azure": "0.1.9", - "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.2.7", - "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.1.7", - "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.1.7", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.18", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.41", - "@backstage/plugin-scaffolder-backend-module-gerrit": "0.1.9", - "@backstage/plugin-scaffolder-backend-module-gitea": "0.1.7", - "@backstage/plugin-scaffolder-backend-module-github": "0.2.7", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.3.3", - "@backstage/plugin-scaffolder-backend-module-rails": "0.4.34", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.25", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.3.0", - "@backstage/plugin-scaffolder-common": "1.5.1", - "@backstage/plugin-scaffolder-node": "0.4.3", - "@backstage/plugin-scaffolder-node-test-utils": "0.1.3", - "@backstage/plugin-scaffolder-react": "1.8.4", - "@backstage/plugin-search": "1.4.10", - "@backstage/plugin-search-backend": "1.5.7", - "@backstage/plugin-search-backend-module-catalog": "0.1.22", - "@backstage/plugin-search-backend-module-elasticsearch": "1.4.0", - "@backstage/plugin-search-backend-module-explore": "0.1.21", - "@backstage/plugin-search-backend-module-pg": "0.5.26", - "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.1.10", - "@backstage/plugin-search-backend-module-techdocs": "0.1.22", - "@backstage/plugin-search-backend-node": "1.2.21", - "@backstage/plugin-search-common": "1.2.11", - "@backstage/plugin-search-react": "1.7.10", - "@backstage/plugin-sentry": "0.5.19", - "@backstage/plugin-shortcuts": "0.3.23", - "@backstage/plugin-signals": "0.0.5", - "@backstage/plugin-signals-backend": "0.1.3", - "@backstage/plugin-signals-node": "0.1.3", - "@backstage/plugin-signals-react": "0.0.3", - "@backstage/plugin-sonarqube": "0.7.16", - "@backstage/plugin-sonarqube-backend": "0.2.19", - "@backstage/plugin-sonarqube-react": "0.1.15", - "@backstage/plugin-splunk-on-call": "0.4.23", - "@backstage/plugin-stack-overflow": "0.1.29", - "@backstage/plugin-stack-overflow-backend": "0.2.21", - "@backstage/plugin-stackstorm": "0.1.15", - "@backstage/plugin-tech-insights": "0.3.26", - "@backstage/plugin-tech-insights-backend": "0.5.31", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.49", - "@backstage/plugin-tech-insights-common": "0.2.12", - "@backstage/plugin-tech-insights-node": "0.6.0", - "@backstage/plugin-tech-radar": "0.7.3", - "@backstage/plugin-techdocs": "1.10.4", - "@backstage/plugin-techdocs-addons-test-utils": "1.0.31", - "@backstage/plugin-techdocs-backend": "1.10.4", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.9", - "@backstage/plugin-techdocs-node": "1.12.3", - "@backstage/plugin-techdocs-react": "1.2.3", - "@backstage/plugin-todo": "0.2.38", - "@backstage/plugin-todo-backend": "0.3.16", - "@backstage/plugin-user-settings": "0.8.5", - "@backstage/plugin-user-settings-backend": "0.2.16", - "@backstage/plugin-vault": "0.1.29", - "@backstage/plugin-vault-backend": "0.4.10", - "@backstage/plugin-vault-node": "0.1.10", - "@backstage/plugin-xcmetrics": "0.2.52", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.0", - "@backstage/plugin-notifications-backend-module-email": "0.0.0", - "example-backend-legacy": "0.2.98-next.1", - "@backstage/plugin-scaffolder-backend-module-notifications": "0.0.0" - }, - "changesets": [ - "afraid-needles-divide", - "blue-hotels-shake", - "brave-carrots-glow", - "bright-pumpkins-rule", - "chatty-cycles-unite", - "chilly-adults-sing", - "chilly-fireants-roll", - "chilly-shoes-doubt", - "cold-cougars-float", - "cold-rats-leave", - "cool-elephants-march", - "create-app-1714476054", - "create-app-1715088359", - "cuddly-chairs-kick", - "curly-shirts-flow", - "curvy-planes-flash", - "cyan-eagles-hammer", - "cyan-suns-shave", - "dirty-chairs-march", - "dry-sloths-impress", - "early-starfishes-hammer", - "eighty-apricots-kneel", - "eighty-bats-stare", - "eleven-pandas-divide", - "empty-beers-relax", - "fix-stackoverflow", - "flat-countries-clap", - "fluffy-hotels-wait", - "four-cooks-serve", - "fresh-crews-impress", - "funny-bees-taste", - "fuzzy-seahorses-tell", - "giant-donkeys-talk", - "gold-waves-bake", - "gorgeous-cameras-cross", - "green-adults-push", - "green-boxes-rescue", - "grumpy-toes-tap", - "happy-radios-kiss", - "healthy-dots-ring", - "healthy-shirts-roll", - "heavy-trainers-fly", - "hip-carrots-drive", - "hot-forks-train", - "itchy-gorillas-hope", - "itchy-keys-wonder", - "kind-toes-scream", - "late-planes-fix", - "lazy-phones-worry", - "little-rockets-live", - "loud-frogs-eat", - "loud-timers-flow", - "loud-vans-greet", - "lovely-games-cry", - "lucky-news-guess", - "mean-ravens-dance", - "metal-years-rhyme", - "new-poets-promise", - "orange-numbers-think", - "perfect-beers-explode", - "perfect-points-hope", - "pink-years-peel", - "proud-comics-love", - "proud-doors-cheat", - "purple-parents-sin", - "purple-waves-smile", - "quick-cats-argue", - "quiet-boxes-build", - "rare-fireants-tickle", - "real-crabs-obey", - "renovate-0d0bd5c", - "rich-adults-float", - "selfish-pigs-glow", - "selfish-walls-visit", - "sharp-glasses-live", - "shy-students-clap", - "silent-wombats-hang", - "six-scissors-smile", - "sixty-bears-camp", - "slimy-kids-behave", - "smart-avocados-invent", - "smooth-garlics-behave", - "sour-socks-approve", - "stupid-onions-know", - "sweet-zoos-clap", - "swift-humans-hunt", - "tall-ads-shave", - "tame-jars-double", - "tasty-apes-learn", - "tasty-moles-jog", - "tasty-rats-explain", - "thick-llamas-itch", - "thick-terms-rush", - "thirty-mangos-travel", - "tough-eggs-wink", - "tricky-cougars-shout", - "unlucky-days-play", - "unlucky-rivers-collect", - "warm-fans-promise", - "wet-files-pretend", - "wild-seahorses-grin", - "young-guests-reflect", - "young-olives-drop" - ] -} diff --git a/.changeset/proud-comics-love.md b/.changeset/proud-comics-love.md deleted file mode 100644 index 6891bf937b..0000000000 --- a/.changeset/proud-comics-love.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-react': minor -'@backstage/plugin-catalog': minor ---- - -Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. diff --git a/.changeset/proud-doors-cheat.md b/.changeset/proud-doors-cheat.md deleted file mode 100644 index 55ab7878d4..0000000000 --- a/.changeset/proud-doors-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/theme': patch ---- - -Fixed bug where scrollbars don't pick up the theme when in dark mode diff --git a/.changeset/purple-parents-sin.md b/.changeset/purple-parents-sin.md deleted file mode 100644 index 4de90893ef..0000000000 --- a/.changeset/purple-parents-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-backstage-openapi': patch ---- - -Fix incorrect dependency import. diff --git a/.changeset/purple-waves-smile.md b/.changeset/purple-waves-smile.md deleted file mode 100644 index 44da72ea70..0000000000 --- a/.changeset/purple-waves-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-guest-provider': patch ---- - -Error if used outside of a development environment without explicit allowance diff --git a/.changeset/quick-cats-argue.md b/.changeset/quick-cats-argue.md deleted file mode 100644 index 956f038f34..0000000000 --- a/.changeset/quick-cats-argue.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-test-utils': patch -'@backstage/backend-defaults': patch -'@backstage/plugin-events-node': patch ---- - -added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used diff --git a/.changeset/quiet-boxes-build.md b/.changeset/quiet-boxes-build.md deleted file mode 100644 index c6f802d5b0..0000000000 --- a/.changeset/quiet-boxes-build.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Fix dark mode text color inside tables in `description:` from OpenAPI definitions diff --git a/.changeset/rare-fireants-tickle.md b/.changeset/rare-fireants-tickle.md deleted file mode 100644 index 8668d4c287..0000000000 --- a/.changeset/rare-fireants-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Added optional `initialShowDropDown` prop to `SidebarSubmenuItem` to internally manage the initial display state of the dropdown items. diff --git a/.changeset/real-crabs-obey.md b/.changeset/real-crabs-obey.md deleted file mode 100644 index 9a52f0e5a6..0000000000 --- a/.changeset/real-crabs-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-explore': patch ---- - -Migrate search collator to use the new auth services. diff --git a/.changeset/red-mangos-fly.md b/.changeset/red-mangos-fly.md deleted file mode 100644 index 73af978be2..0000000000 --- a/.changeset/red-mangos-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github-org': patch ---- - -Fixed an issue where the `catalog-backend-module-github-org` would not correctly create groups using `default` as namespace in case a single organization was configured. diff --git a/.changeset/renovate-0d0bd5c.md b/.changeset/renovate-0d0bd5c.md deleted file mode 100644 index ac3fe697f9..0000000000 --- a/.changeset/renovate-0d0bd5c.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-home-react': patch -'@backstage/plugin-home': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-scaffolder': patch ---- - -Updated dependency `@rjsf/utils` to `5.18.2`. -Updated dependency `@rjsf/core` to `5.18.2`. -Updated dependency `@rjsf/material-ui` to `5.18.2`. -Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. diff --git a/.changeset/renovate-228c530.md b/.changeset/renovate-228c530.md deleted file mode 100644 index e1838b3481..0000000000 --- a/.changeset/renovate-228c530.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-bitbucket-cloud-common': patch ---- - -Updated dependency `ts-morph` to `^22.0.0`. diff --git a/.changeset/rich-adults-float.md b/.changeset/rich-adults-float.md deleted file mode 100644 index 5a9746b8f5..0000000000 --- a/.changeset/rich-adults-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -Updated to use the new `catalogAnalysisExtensionPoint` API. diff --git a/.changeset/selfish-pigs-glow.md b/.changeset/selfish-pigs-glow.md deleted file mode 100644 index c7808c0283..0000000000 --- a/.changeset/selfish-pigs-glow.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-auth-node': patch ---- - -Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` diff --git a/.changeset/selfish-walls-visit.md b/.changeset/selfish-walls-visit.md deleted file mode 100644 index 64c87a64f5..0000000000 --- a/.changeset/selfish-walls-visit.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines. -Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card. diff --git a/.changeset/sharp-glasses-live.md b/.changeset/sharp-glasses-live.md deleted file mode 100644 index 000f49fc47..0000000000 --- a/.changeset/sharp-glasses-live.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Allow passing a `--require` argument through to the Node process during `package start` diff --git a/.changeset/shy-students-clap.md b/.changeset/shy-students-clap.md deleted file mode 100644 index 0e6244f21d..0000000000 --- a/.changeset/shy-students-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-gitlab-org': patch ---- - -Added a new `catalog-backend-module-gitlab-org` module which adds the `GitlabOrgDiscoveryEntityProvider` to the catalog's providers using the new backend system. diff --git a/.changeset/silent-wombats-hang.md b/.changeset/silent-wombats-hang.md deleted file mode 100644 index 85bda32007..0000000000 --- a/.changeset/silent-wombats-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Handle fetching huge amounts of users from Azure without crashing diff --git a/.changeset/six-scissors-smile.md b/.changeset/six-scissors-smile.md deleted file mode 100644 index 7680edbba6..0000000000 --- a/.changeset/six-scissors-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -The `SignInPage` guest provider will now fall back to legacy guest auth if the backend request fails, allowing guest auth without a running backend. diff --git a/.changeset/sixty-bears-camp.md b/.changeset/sixty-bears-camp.md deleted file mode 100644 index f7a11482a7..0000000000 --- a/.changeset/sixty-bears-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fixed a bug where the `MultiEntityPicker` was not able to be set as required diff --git a/.changeset/slimy-donkeys-laugh.md b/.changeset/slimy-donkeys-laugh.md deleted file mode 100644 index c18102e68c..0000000000 --- a/.changeset/slimy-donkeys-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -The Backstage identity session expiration check will no longer fall back to using the provider expiration. This was introduced to smooth out the rollout of Backstage release 1.18, and is no longer needed. diff --git a/.changeset/slimy-kids-behave.md b/.changeset/slimy-kids-behave.md deleted file mode 100644 index 5e635878ff..0000000000 --- a/.changeset/slimy-kids-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows diff --git a/.changeset/smart-avocados-invent.md b/.changeset/smart-avocados-invent.md deleted file mode 100644 index c71b6fef37..0000000000 --- a/.changeset/smart-avocados-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-dynamic-feature-service': patch ---- - -Updates the `scanRoot` method in the `PluginScanner` class to specifically ignore the `lost+found` directory, which is a system-generated directory used for file recovery on Unix-like systems. Skipping this directory avoids unnecessary errors. diff --git a/.changeset/smooth-garlics-behave.md b/.changeset/smooth-garlics-behave.md deleted file mode 100644 index ed972ae164..0000000000 --- a/.changeset/smooth-garlics-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added option to `ServerTokenManager.fromConfig` that allows it to be instantiated in production without any configured keys. diff --git a/.changeset/sour-socks-approve.md b/.changeset/sour-socks-approve.md deleted file mode 100644 index db40806280..0000000000 --- a/.changeset/sour-socks-approve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -Emit well known relationships for the Domain entity kind. diff --git a/.changeset/strange-rocks-study.md b/.changeset/strange-rocks-study.md deleted file mode 100644 index 75ceb745fb..0000000000 --- a/.changeset/strange-rocks-study.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-node': patch ---- - -To remove the dependency on the soon-to-be-deprecated `backend-common` package, this package now maintains its own isomorphic Git class implementation. diff --git a/.changeset/stupid-onions-know.md b/.changeset/stupid-onions-know.md deleted file mode 100644 index 3d028557be..0000000000 --- a/.changeset/stupid-onions-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Empty descriptions are not rendered to improve the look&feel. diff --git a/.changeset/sweet-spiders-rhyme.md b/.changeset/sweet-spiders-rhyme.md deleted file mode 100644 index 71dcc375c0..0000000000 --- a/.changeset/sweet-spiders-rhyme.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Removing react-text-truncate with css styles. diff --git a/.changeset/sweet-zoos-clap.md b/.changeset/sweet-zoos-clap.md deleted file mode 100644 index 8c76f4abd7..0000000000 --- a/.changeset/sweet-zoos-clap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Fixed bug in stitching queue gauge that included entities that are scheduled in the future. diff --git a/.changeset/swift-humans-hunt.md b/.changeset/swift-humans-hunt.md deleted file mode 100644 index b96b3e41bc..0000000000 --- a/.changeset/swift-humans-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Avoiding pre-loading display total count undefined for table counts diff --git a/.changeset/tall-ads-shave.md b/.changeset/tall-ads-shave.md deleted file mode 100644 index 17d58452eb..0000000000 --- a/.changeset/tall-ads-shave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': patch ---- - -Add possibility to generate random notifications on the fly in local development diff --git a/.changeset/tame-jars-double.md b/.changeset/tame-jars-double.md deleted file mode 100644 index 8af92a2d72..0000000000 --- a/.changeset/tame-jars-double.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-gitlab': minor ---- - -Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests diff --git a/.changeset/tame-jokes-bow.md b/.changeset/tame-jokes-bow.md deleted file mode 100644 index e08cf9ca0c..0000000000 --- a/.changeset/tame-jokes-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-graph': patch ---- - -Allow multiple edges with different type (e.g. `ownedBy` and `applicationOwnerBy`) to have the same source and target node. diff --git a/.changeset/tasty-apes-learn.md b/.changeset/tasty-apes-learn.md deleted file mode 100644 index 7a4545cf10..0000000000 --- a/.changeset/tasty-apes-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Fix to show web notifications even when browser is on foreground. Fix duplicate notifications with multiple tabs. diff --git a/.changeset/tasty-moles-jog.md b/.changeset/tasty-moles-jog.md deleted file mode 100644 index f28b77337e..0000000000 --- a/.changeset/tasty-moles-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`. diff --git a/.changeset/tasty-rats-explain.md b/.changeset/tasty-rats-explain.md deleted file mode 100644 index b97ee28fef..0000000000 --- a/.changeset/tasty-rats-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': minor ---- - -Added `HarnessIntegration` via the `ScmIntegrations` interface. diff --git a/.changeset/tender-falcons-add.md b/.changeset/tender-falcons-add.md deleted file mode 100644 index 60a947bcc3..0000000000 --- a/.changeset/tender-falcons-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Fixed the dynamic import of vite. diff --git a/.changeset/thick-llamas-itch.md b/.changeset/thick-llamas-itch.md deleted file mode 100644 index 17d4fb23b6..0000000000 --- a/.changeset/thick-llamas-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Allow defining `className` and additional properties for `NotificationsSideBarItem` diff --git a/.changeset/thick-terms-rush.md b/.changeset/thick-terms-rush.md deleted file mode 100644 index fb12f11f7e..0000000000 --- a/.changeset/thick-terms-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-react': patch ---- - -add the namespace label to CronJobDrawer & IngressDrawer. diff --git a/.changeset/thirty-mangos-travel.md b/.changeset/thirty-mangos-travel.md deleted file mode 100644 index 2281cc06b6..0000000000 --- a/.changeset/thirty-mangos-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search': patch ---- - -Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal. diff --git a/.changeset/tough-eggs-wink.md b/.changeset/tough-eggs-wink.md deleted file mode 100644 index 52644e3620..0000000000 --- a/.changeset/tough-eggs-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-node': minor ---- - -Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden. diff --git a/.changeset/tricky-cougars-shout.md b/.changeset/tricky-cougars-shout.md deleted file mode 100644 index eed14c9eb8..0000000000 --- a/.changeset/tricky-cougars-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-explore': patch ---- - -Update README.md to point to explore plugin in community-plugins repository. diff --git a/.changeset/unlucky-days-play.md b/.changeset/unlucky-days-play.md deleted file mode 100644 index b847ebd2fc..0000000000 --- a/.changeset/unlucky-days-play.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Fix infinite loop in the notification title counter diff --git a/.changeset/unlucky-rivers-collect.md b/.changeset/unlucky-rivers-collect.md deleted file mode 100644 index c5ec865658..0000000000 --- a/.changeset/unlucky-rivers-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend': patch ---- - -Updated documentation for sending messages by external services. diff --git a/.changeset/warm-fans-promise.md b/.changeset/warm-fans-promise.md deleted file mode 100644 index a8a8f8a2c7..0000000000 --- a/.changeset/warm-fans-promise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -The rendered size of a notification is limited for very long descriptions. diff --git a/.changeset/wet-files-pretend.md b/.changeset/wet-files-pretend.md deleted file mode 100644 index 8f77a5bc0b..0000000000 --- a/.changeset/wet-files-pretend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Fix ownership card sometimes locking up for complex org structures diff --git a/.changeset/wild-cats-hug.md b/.changeset/wild-cats-hug.md deleted file mode 100644 index e33d399dab..0000000000 --- a/.changeset/wild-cats-hug.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -Added `alwaysUseDefaultNamespace` option to `GithubMultiOrgEntityProvider`. - -If set to true, the provider will use `default` as the namespace for all group entities. Groups with the same name across different orgs will be considered the same group. diff --git a/.changeset/wild-seahorses-grin.md b/.changeset/wild-seahorses-grin.md deleted file mode 100644 index 94b30a6839..0000000000 --- a/.changeset/wild-seahorses-grin.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-techdocs': patch -'@backstage/plugin-search-backend-module-catalog': patch ---- - -Allow the `tokenManager` parameter to be optional when instantiating collator diff --git a/.changeset/young-guests-reflect.md b/.changeset/young-guests-reflect.md deleted file mode 100644 index 355c38bc21..0000000000 --- a/.changeset/young-guests-reflect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications': patch ---- - -Add option to set the notification as read automatically when the notification link is opened diff --git a/.changeset/young-olives-drop.md b/.changeset/young-olives-drop.md deleted file mode 100644 index 4058293be8..0000000000 --- a/.changeset/young-olives-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -Removed explicit `toString()` method from `ServiceRef` type. diff --git a/docs/releases/v1.27.0-changelog.md b/docs/releases/v1.27.0-changelog.md new file mode 100644 index 0000000000..28e52caa7b --- /dev/null +++ b/docs/releases/v1.27.0-changelog.md @@ -0,0 +1,1949 @@ +# Release v1.27.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.27.0](https://backstage.github.io/upgrade-helper/?to=1.27.0) + +## @backstage/backend-common@0.22.0 + +### Minor Changes + +- ed83f85: Internal refactor of the database code. + + **BREAKING**: The helper functions `createDatabaseClient` and `ensureDatabaseExists` have been removed from the public interface, since they have no usage within the repository and never were suitable for calling from the outside. Please consider using `coreServices.database` or `DatabaseManager` directly wherever possible instead. + +### Patch Changes + +- 2cc750d: Added `HarnessURLReader` with `readUrl` support. +- 57f692e: Preparing for a stable new backend system release, we are deprecating utilities in the `backend-common` that are not used by the core framework, such as the isomorphic `Git` class. As we will no longer support the isomorphic `Git` utility in the framework packages, we recommend plugins that start maintaining their own implementation of this class. +- 0ec0796: Plugins created through the `legacyPlugin` helper are now able to authenticate requests from plugins that are fully implemented using the new backend system. This fixes the `Key for the ES256 algorithm must be one of type KeyObject or CryptoKey. Received an instance of Uint8Array` error. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- ccc8851: Added config prop `ensureSchemaExists` to support postgres instances where user can create schemas but not databases. +- f66bbb4: Only create a single actual connection to memcache/redis, even in cases where many `CacheService` instances are made +- ba0b8b4: Added option to `ServerTokenManager.fromConfig` that allows it to be instantiated in production without any configured keys. +- Updated dependencies + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/integration@1.11.0 + +## @backstage/catalog-model@1.5.0 + +### Minor Changes + +- 79025f3: Introduce a domain attribute to the domain entity to allow a hierarchy of domains to exist. + +## @backstage/frontend-app-api@0.7.0 + +### Minor Changes + +- ddddecb: Extensions in app-config now always affect ordering. Previously, only when enabling disabled extensions did they rise to the top. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/integration@1.11.0 + +### Minor Changes + +- 2cc750d: Added `HarnessIntegration` via the `ScmIntegrations` interface. + +## @backstage/repo-tools@0.9.0 + +### Minor Changes + +- 683870a: Adds 2 new commands `repo schema openapi diff` and `package schema openapi diff`. `repo schema openapi diff` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes. They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. + +### Patch Changes + +- 9ae9bb2: Update the paths logic in the api reports command to support complex subpaths +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + +## @backstage/plugin-catalog@1.20.0 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- 5d99272: Update local development dependencies. +- 4118530: Avoiding pre-loading display total count undefined for table counts +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-search-react@1.7.11 + +## @backstage/plugin-catalog-backend@1.22.0 + +### Minor Changes + +- f2a2a83: Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`. +- f2a2a83: The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API. +- 8d14475: Emit well known relationships for the Domain entity kind. + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- c6cb568: Add lifecycle monitoring for the catalog processing +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 8479a0b: Fixed bug in stitching queue gauge that included entities that are scheduled in the future. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-search-backend-module-catalog@0.1.24 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/integration@1.11.0 + - @backstage/backend-openapi-utils@0.1.11 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/plugin-catalog-import@0.11.0 + +### Minor Changes + +- e1174b0: `EntityListComponent` uses `entityPresentationApi` instead of `humanizeEntityRef` to display Entity + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-node@1.12.0 + +### Minor Changes + +- f2a2a83: Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`. +- f2a2a83: Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/plugin-catalog-react@1.12.0 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-scaffolder@1.20.0 + +### Minor Changes + +- 4268696: `MultiEntityPicker` uses `EntityDisplayName` instead of `humanizeEntityRef` to display entity. + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- 131e5cb: Fix broken links in README. +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- 762141c: Fixed a bug where the `MultiEntityPicker` was not able to be set as required +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-scaffolder-react@1.8.5 + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0 + +### Minor Changes + +- 18f736f: Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests + +### Patch Changes + +- 8fa8a00: Add merge method and squash option for project creation +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- ffc73ec: Add examples for `gitlab:repo:push` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/app-defaults@1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + +## @backstage/backend-app-api@0.7.3 + +### Patch Changes + +- 4cd5ff0: Add ability to configure the Node.js HTTP Server when configuring the root HTTP Router service + +- e8199b1: Move the JWKS registration outside of the lifecycle middleware + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. + +- dc8c5dd: The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests. + +- 025641b: Redact `meta` fields too with the logger + +- 09f8988: Remove explicit `alg` check for user tokens in `verifyToken` + +- 5863e02: Internal refactor to only create one external token handler + +- a1dc547: Added support for camel case CSP directives in app-config. For example: + + ```yaml + backend: + csp: + upgradeInsecureRequests: false + ``` + +- 329cc34: Added logging of all plugins being initialized, periodic status, and completion. + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/backend-defaults@0.2.18 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/backend-dynamic-feature-service@0.2.10 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- b611fd0: Updates the `scanRoot` method in the `PluginScanner` class to specifically ignore the `lost+found` directory, which is a system-generated directory used for file recovery on Unix-like systems. Skipping this directory avoids unnecessary errors. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-app-node@0.1.18 + - @backstage/plugin-events-backend@0.3.5 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/backend-openapi-utils@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + +## @backstage/backend-plugin-api@0.6.18 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 1fedf0c: Removed explicit `toString()` method from `ServiceRef` type. +- Updated dependencies + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/backend-tasks@0.5.23 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + +## @backstage/backend-test-utils@0.3.8 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/catalog-client@1.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + +## @backstage/cli@0.26.5 + +### Patch Changes + +- b8f1fc2: The `build-workspace` command no longer manually runs `yarn postpack`, relying instead on the fact that running `yarn pack` will automatically invoke the `postpack` script. No action is necessary if you are running the latest version of yarn 1, 3, or 4. +- fcd3462: Both the target and types library have been bumped from ES2021 to ES2022 in `@backstage/cli/config/tsconfig.json`. +- 0cc5ed3: Add support for `versions:migrate` to do code changes. Can be skipped with `--no-code-changes` +- f97ad04: Add preserveModules to rollup, which allows better async loading and tree-shaking in webpack +- 2a6f10d: The `versions:bump` command will no longer exit with a non-zero status if the version bump fails due to forbidden duplicate package installations. It will now also provide more information about how to troubleshoot such an error. The set of forbidden duplicates has also been expanded to include all `@backstage/*-app-api` packages. +- c5d7b40: Allow passing a `--require` argument through to the Node process during `package start` +- cc3c518: Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows +- 812dff0: Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`. +- f185603: Fixed the dynamic import of vite. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.11.0 + +## @backstage/core-app-api@1.12.5 + +### Patch Changes + +- 1bed9a3: The Backstage identity session expiration check will no longer fall back to using the provider expiration. This was introduced to smooth out the rollout of Backstage release 1.18, and is no longer needed. + +## @backstage/core-compat-api@0.2.5 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/core-components@0.14.7 + +### Patch Changes + +- e42779e: Properly log the `errorInfo` in `ErrorBoundary` +- 5223c4c: Fixed an internal circular import that broke Jest mocks. +- 71e4229: Added optional `initialShowDropDown` prop to `SidebarSubmenuItem` to internally manage the initial display state of the dropdown items. +- a2ee4df: Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines. + Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card. +- 5b7b49b: The `SignInPage` guest provider will now fall back to legacy guest auth if the backend request fails, allowing guest auth without a running backend. +- 359376a: Removing react-text-truncate with css styles. +- Updated dependencies + - @backstage/theme@0.5.4 + +## @backstage/create-app@0.5.15 + +### Patch Changes + +- c066c88: Removed `packages/backend/src/types.ts` from the template as it is unused. It was mistakenly left in after moving the template to the new backend system. +- 5d99272: Update local development dependencies. +- 0478509: Bumped create-app version. +- d85dd88: Bumped create-app version. +- 8105aad: Removed the Tech Radar and GitHub Actions plugins from the template, which have been moved to the community plugins repository. + +## @backstage/dev-utils@1.0.32 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/app-defaults@1.5.5 + - @backstage/integration-react@1.1.27 + +## @backstage/eslint-plugin@0.1.8 + +### Patch Changes + +- 65ec043: add some `pickers` fixes + +## @backstage/frontend-plugin-api@0.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + +## @backstage/frontend-test-utils@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/test-utils@1.5.5 + +## @backstage/integration-react@1.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.11.0 + +## @techdocs/cli@1.8.11 + +### Patch Changes + +- 1a0e009: Fix cookie endpoint mock for `serve` +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-techdocs-node@1.12.4 + +## @backstage/test-utils@1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + +## @backstage/theme@0.5.4 + +### Patch Changes + +- f1462df: Fixed bug where scrollbars don't pick up the theme when in dark mode + +## @backstage/plugin-api-docs@0.11.5 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- 725ff0b: Fix dark mode text color inside tables in `description:` from OpenAPI definitions +- Updated dependencies + - @backstage/plugin-catalog@1.20.0 + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-app-backend@0.3.66 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-app-node@0.1.18 + +## @backstage/plugin-app-node@0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + +## @backstage/plugin-app-visualizer@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/plugin-auth-backend@0.22.5 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.9 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15 + - @backstage/plugin-auth-backend-module-google-provider@0.1.15 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.11 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.15 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.4 + +### Patch Changes + +- 07d8cca: Error if used outside of a development environment without explicit allowance +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.1.9 + +### Patch Changes + +- dd53bf3: Add nonce to authorize request to be added in ID token +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-auth-node@0.4.13 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + +## @backstage/plugin-auth-react@0.1.2 + +### Patch Changes + +- c297afd: When using `CookieAuthRefreshProvider` or `useCookieAuthRefresh`, a 404 response from the cookie endpoint will now be treated as if cookie auth is disabled and is not needed. +- Updated dependencies + - @backstage/core-components@0.14.7 + +## @backstage/plugin-bitbucket-cloud-common@0.2.19 + +### Patch Changes + +- d76cb29: Updated dependency `ts-morph` to `^22.0.0`. +- Updated dependencies + - @backstage/integration@1.11.0 + +## @backstage/plugin-catalog-backend-module-aws@0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-catalog-backend-module-azure@0.1.38 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1 + +### Patch Changes + +- f3f0281: Fix incorrect dependency import. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/backend-openapi-utils@0.1.11 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-bitbucket-cloud-common@0.2.19 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.32 + +### Patch Changes + +- 062ffb1: Allow skipping archived repositories (`skipArchivedRepos` flag) on Bitbucket. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.35 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + +## @backstage/plugin-catalog-backend-module-github@0.6.1 + +### Patch Changes + +- 0b50143: GitHub push events now schedule a refresh on entities that have a `refresh_key` matching the `catalogPath` config itself. + This allows to support a `catalogPath` configuration that uses glob patterns. + +- f2a2a83: Updated to use the new `catalogAnalysisExtensionPoint` API. + +- 5bdeaa7: Added `alwaysUseDefaultNamespace` option to `GithubMultiOrgEntityProvider`. + + If set to true, the provider will use `default` as the namespace for all group entities. Groups with the same name across different orgs will be considered the same group. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.13 + +### Patch Changes + +- 5bdeaa7: Fixed an issue where the `catalog-backend-module-github-org` would not correctly create groups using `default` as namespace in case a single organization was configured. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-catalog-backend-module-github@0.6.1 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.15 + +### Patch Changes + +- a70377d: Added events support for `GitlabDiscoveryEntityProvider` and `GitlabOrgDiscoveryEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.1 + +### Patch Changes + +- a70377d: Added a new `catalog-backend-module-gitlab-org` module which adds the `GitlabOrgDiscoveryEntityProvider` to the catalog's providers using the new backend system. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.23 + +### Patch Changes + +- 8c1ab9e: Fix plugin/incremental-ingestion 'Maximum call stack size exceeded' error when ingest large entities. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.34 + +### Patch Changes + +- 7699967: Remove dependency to Winston Logger and use Backstage LoggerService instead +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.26 + +### Patch Changes + +- 49eab29: Fixed disabling of user photo fetching. Previously, the config value wasn't propagated properly, so user photos was still being fetched despite disabled by config. +- 6e370e6: Handle fetching huge amounts of users from Azure without crashing +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.5 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- 6f5a3a3: Correctly convert owner to string in case owner has not been provided +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-catalog-common@1.0.23 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-catalog-graph@0.4.5 + +### Patch Changes + +- 39564b3: Allow multiple edges with different type (e.g. `ownedBy` and `applicationOwnerBy`) to have the same source and target node. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-config-schema@0.1.55 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + +## @backstage/plugin-devtools@0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/plugin-devtools-backend@0.3.4 + +### Patch Changes + +- 036feca: Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/plugin-events-backend@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-backend-module-azure@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-backend-module-gerrit@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-backend-module-github@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-backend-module-gitlab@0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-backend-test-utils@0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4 + +## @backstage/plugin-events-node@0.3.4 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + +## @backstage/plugin-home@0.7.4 + +### Patch Changes + +- 2196d3e: Use relative time when displaying visits from the same day +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/plugin-home-react@0.1.13 + - @backstage/core-app-api@1.12.5 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/plugin-home-react@0.1.13 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-components@0.14.7 + +## @backstage/plugin-kubernetes@0.11.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/plugin-kubernetes-react@0.3.5 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-kubernetes-backend@0.17.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-kubernetes-common@0.7.6 + - @backstage/plugin-kubernetes-node@0.1.12 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/plugin-kubernetes-cluster@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/plugin-kubernetes-react@0.3.5 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-kubernetes-common@0.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-kubernetes-node@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-kubernetes-react@0.3.5 + +### Patch Changes + +- 3102a99: add the namespace label to CronJobDrawer & IngressDrawer. +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-notifications@0.2.1 + +### Patch Changes + +- e6bf85f: Allow overriding `NotificationsPage` page properties +- f730c0b: The user can newly mark all unread messages as read at one click. +- bfcb2f1: Allow showing notifications as snackbars in the UI +- e49a810: Show all notifications by default to match the sidebar item status +- 42eaf63: Increase default and allow modifying notification snackbar auto hide duration +- a42a19b: Empty descriptions are not rendered to improve the look&feel. +- 1bc3b86: Fix to show web notifications even when browser is on foreground. Fix duplicate notifications with multiple tabs. +- f793112: Allow defining `className` and additional properties for `NotificationsSideBarItem` +- e1c7d6e: Fix infinite loop in the notification title counter +- fcda449: The rendered size of a notification is limited for very long descriptions. +- f6633ca: Add option to set the notification as read automatically when the notification link is opened +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + +## @backstage/plugin-notifications-backend@0.2.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- cba628a: Add possibility to generate random notifications on the fly in local development +- ee09dfc: Updated documentation for sending messages by external services. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-notifications-node@0.1.4 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-signals-node@0.1.4 + +## @backstage/plugin-notifications-backend-module-email@0.0.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- e538b10: Support relative links in notifications sent via email +- dbf2696: Allow sending notifications by email with the new notifications module +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-notifications-node@0.1.4 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + +## @backstage/plugin-notifications-node@0.1.4 + +### Patch Changes + +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-signals-node@0.1.4 + +## @backstage/plugin-org@0.6.25 + +### Patch Changes + +- 99e6105: Fix ownership card sometimes locking up for complex org structures +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-org-react@0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/catalog-client@1.6.5 + +## @backstage/plugin-permission-backend@0.5.42 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/plugin-permission-node@0.7.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-proxy-backend@0.4.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + +## @backstage/plugin-scaffolder-backend@1.22.6 + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- 025641b: Fix issue with the log format not being respected when logging from actions +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- e4b50ab: Scaffolder workspace serialization +- 025641b: Redact `meta` fields too with the logger +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16 + - @backstage/plugin-permission-node@0.7.29 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8 + - @backstage/integration@1.11.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8 + +### Patch Changes + +- 24dd655: Add examples for `publish:bitbucketServer:pull-request` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.42 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.8 + +### Patch Changes + +- 554af73: Allow defining `repoVisibility` field for the action `publish:gitea` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.2.8 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 52ab241: Adding support to change the default commit author for `publish:github:pull-request` +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.1 + +### Patch Changes + +- 503d769: Add a new scaffolder action to allow sending notifications from templates +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/plugin-notifications-node@0.1.4 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.35 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/plugin-scaffolder-node-test-utils@0.1.4 + +## @backstage/plugin-scaffolder-common@1.5.2 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-scaffolder-node@0.4.4 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- e4b50ab: Scaffolder workspace serialization +- f633efa: To remove the dependency on the soon-to-be-deprecated `backend-common` package, this package now maintains its own isomorphic Git class implementation. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-test-utils@0.3.8 + - @backstage/plugin-scaffolder-node@0.4.4 + +## @backstage/plugin-scaffolder-react@1.8.5 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/catalog-client@1.6.5 + +## @backstage/plugin-search@1.4.11 + +### Patch Changes + +- 0501243: Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/plugin-search-react@1.7.11 + +## @backstage/plugin-search-backend@1.5.8 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/repo-tools@0.9.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/backend-openapi-utils@0.1.11 + - @backstage/plugin-permission-node@0.7.29 + +## @backstage/plugin-search-backend-module-catalog@0.1.24 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- d5fff66: Fix wiring of the module exported at the `/alpha` path, which was causing authentication failures. +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-search-backend-module-elasticsearch@1.4.1 + +### Patch Changes + +- 5252ee1: Fix never resolved indexer promise. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-search-backend-node@1.2.22 + +## @backstage/plugin-search-backend-module-explore@0.1.24 + +### Patch Changes + +- ca6e2e0: Migrate search collator to use the new auth services. +- 5d99272: Update README.md to point to explore plugin in community-plugins repository. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + +## @backstage/plugin-search-backend-module-pg@0.5.27 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-search-backend-node@1.2.22 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + +## @backstage/plugin-search-backend-module-techdocs@0.1.23 + +### Patch Changes + +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-techdocs-node@1.12.4 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-search-backend-node@1.2.22 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + +## @backstage/plugin-search-react@1.7.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/plugin-signals@0.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + +## @backstage/plugin-signals-backend@0.1.4 + +### Patch Changes + +- 845d56a: Improved signal lifecycle management and added server side pinging of connections +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-signals-node@0.1.4 + +## @backstage/plugin-signals-node@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + +## @backstage/plugin-techdocs@1.10.5 + +### Patch Changes + +- d2cc139: Update path in Readme for Plugin Techdocs to show the correct setup information. +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/integration@1.11.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-techdocs-react@1.2.4 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.32 + +### Patch Changes + +- 2f13862: Fix bug in TechDocsAddonTester when jest.resetAllMocks is called between tests +- Updated dependencies + - @backstage/plugin-catalog@1.20.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/core-app-api@1.12.5 + - @backstage/integration-react@1.1.27 + - @backstage/test-utils@1.5.5 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-techdocs-react@1.2.4 + +## @backstage/plugin-techdocs-backend@1.10.5 + +### Patch Changes + +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-techdocs-node@1.12.4 + - @backstage/integration@1.11.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-techdocs-react@1.2.4 + +## @backstage/plugin-techdocs-node@1.12.4 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/integration@1.11.0 + +## @backstage/plugin-techdocs-react@1.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-user-settings@0.8.6 + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/frontend-plugin-api@0.6.5 + +## @backstage/plugin-user-settings-backend@0.2.17 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + +## example-app@0.2.97 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5 + - @backstage/plugin-scaffolder@1.20.0 + - @backstage/plugin-user-settings@0.8.6 + - @backstage/plugin-catalog@1.20.0 + - @backstage/plugin-notifications@0.2.1 + - @backstage/plugin-api-docs@0.11.5 + - @backstage/plugin-home@0.7.4 + - @backstage/cli@0.26.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/plugin-catalog-import@0.11.0 + - @backstage/frontend-app-api@0.7.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/plugin-catalog-graph@0.4.5 + - @backstage/plugin-search@1.4.11 + - @backstage/plugin-org@0.6.25 + - @backstage/app-defaults@1.5.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4 + - @backstage/plugin-devtools@0.1.14 + - @backstage/plugin-kubernetes@0.11.10 + - @backstage/plugin-kubernetes-cluster@0.0.11 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-signals@0.0.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10 + - @backstage/plugin-techdocs-react@1.2.4 + +## example-app-next@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5 + - @backstage/plugin-scaffolder@1.20.0 + - @backstage/plugin-user-settings@0.8.6 + - @backstage/plugin-catalog@1.20.0 + - @backstage/plugin-notifications@0.2.1 + - @backstage/core-compat-api@0.2.5 + - @backstage/plugin-api-docs@0.11.5 + - @backstage/plugin-home@0.7.4 + - @backstage/cli@0.26.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/plugin-catalog-import@0.11.0 + - @backstage/frontend-app-api@0.7.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/plugin-catalog-graph@0.4.5 + - @backstage/plugin-search@1.4.11 + - @backstage/plugin-org@0.6.25 + - @backstage/app-defaults@1.5.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-app-visualizer@0.1.6 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4 + - @backstage/plugin-kubernetes@0.11.10 + - @backstage/plugin-kubernetes-cluster@0.0.11 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-signals@0.0.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10 + - @backstage/plugin-techdocs-react@1.2.4 + +## app-next-example-plugin@0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/frontend-plugin-api@0.6.5 + +## example-backend@0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.22.6 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.1.24 + - @backstage/plugin-notifications-backend@0.2.1 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-app-backend@0.3.66 + - @backstage/plugin-devtools-backend@0.3.4 + - @backstage/plugin-signals-backend@0.1.4 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/plugin-search-backend@1.5.8 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15 + - @backstage/plugin-techdocs-backend@1.10.5 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4 + - @backstage/backend-defaults@0.2.18 + - @backstage/plugin-search-backend-module-explore@0.1.24 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-search-backend-module-techdocs@0.1.23 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16 + - @backstage/plugin-kubernetes-backend@0.17.1 + - @backstage/plugin-permission-backend@0.5.42 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15 + - @backstage/plugin-permission-node@0.7.29 + - @backstage/plugin-proxy-backend@0.4.16 + +## example-backend-legacy@0.2.98 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1 + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-scaffolder-backend@1.22.6 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.1.24 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-search-backend-module-pg@0.5.27 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-app-backend@0.3.66 + - @backstage/plugin-devtools-backend@0.3.4 + - @backstage/plugin-signals-backend@0.1.4 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/plugin-search-backend@1.5.8 + - @backstage/plugin-techdocs-backend@1.10.5 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-search-backend-module-explore@0.1.24 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/integration@1.11.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23 + - example-app@0.2.97 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16 + - @backstage/plugin-events-backend@0.3.5 + - @backstage/plugin-kubernetes-backend@0.17.1 + - @backstage/plugin-permission-backend@0.5.42 + - @backstage/plugin-permission-node@0.7.29 + - @backstage/plugin-proxy-backend@0.4.16 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35 + - @backstage/plugin-signals-node@0.1.4 + +## e2e-test@0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.15 + +## techdocs-cli-embedded-app@0.2.96 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.20.0 + - @backstage/cli@0.26.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/app-defaults@1.5.5 + - @backstage/integration-react@1.1.27 + - @backstage/test-utils@1.5.5 + - @backstage/plugin-techdocs-react@1.2.4 + +## @internal/plugin-todo-list@1.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + +## @internal/plugin-todo-list-backend@1.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 diff --git a/package.json b/package.json index 8611c9d59b..53c87544af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.27.0-next.2", + "version": "1.27.0", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 15f1c5bd67..5ab386f512 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/app-defaults +## 1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + ## 1.5.5-next.1 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index d5955d863e..27533f2b9f 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.5-next.1", + "version": "1.5.5", "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 fa7d0f3107..356ed1f378 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.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/frontend-plugin-api@0.6.5 + ## 0.0.11-next.1 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index 55dd01aea5..e601961ef6 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.11-next.1", + "version": "0.0.11", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin" diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 890f1ca20b..3f03cac54f 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,44 @@ # example-app-next +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5 + - @backstage/plugin-scaffolder@1.20.0 + - @backstage/plugin-user-settings@0.8.6 + - @backstage/plugin-catalog@1.20.0 + - @backstage/plugin-notifications@0.2.1 + - @backstage/core-compat-api@0.2.5 + - @backstage/plugin-api-docs@0.11.5 + - @backstage/plugin-home@0.7.4 + - @backstage/cli@0.26.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/plugin-catalog-import@0.11.0 + - @backstage/frontend-app-api@0.7.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/plugin-catalog-graph@0.4.5 + - @backstage/plugin-search@1.4.11 + - @backstage/plugin-org@0.6.25 + - @backstage/app-defaults@1.5.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-app-visualizer@0.1.6 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4 + - @backstage/plugin-kubernetes@0.11.10 + - @backstage/plugin-kubernetes-cluster@0.0.11 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-signals@0.0.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10 + - @backstage/plugin-techdocs-react@1.2.4 + ## 0.0.11-next.2 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index d7b286e0fa..d5f69915a9 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.11-next.2", + "version": "0.0.11", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 3b8d6ca936..fa01c31fd6 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,42 @@ # example-app +## 0.2.97 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5 + - @backstage/plugin-scaffolder@1.20.0 + - @backstage/plugin-user-settings@0.8.6 + - @backstage/plugin-catalog@1.20.0 + - @backstage/plugin-notifications@0.2.1 + - @backstage/plugin-api-docs@0.11.5 + - @backstage/plugin-home@0.7.4 + - @backstage/cli@0.26.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/plugin-catalog-import@0.11.0 + - @backstage/frontend-app-api@0.7.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/plugin-catalog-graph@0.4.5 + - @backstage/plugin-search@1.4.11 + - @backstage/plugin-org@0.6.25 + - @backstage/app-defaults@1.5.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4 + - @backstage/plugin-devtools@0.1.14 + - @backstage/plugin-kubernetes@0.11.10 + - @backstage/plugin-kubernetes-cluster@0.0.11 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-signals@0.0.6 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10 + - @backstage/plugin-techdocs-react@1.2.4 + ## 0.2.97-next.2 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 13466e1b41..9978c6fcb4 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.97-next.2", + "version": "0.2.97", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 733a19d81e..96259c80da 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/backend-app-api +## 0.7.3 + +### Patch Changes + +- 4cd5ff0: Add ability to configure the Node.js HTTP Server when configuring the root HTTP Router service +- e8199b1: Move the JWKS registration outside of the lifecycle middleware +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- dc8c5dd: The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests. +- 025641b: Redact `meta` fields too with the logger +- 09f8988: Remove explicit `alg` check for user tokens in `verifyToken` +- 5863e02: Internal refactor to only create one external token handler +- a1dc547: Added support for camel case CSP directives in app-config. For example: + + ```yaml + backend: + csp: + upgradeInsecureRequests: false + ``` + +- 329cc34: Added logging of all plugins being initialized, periodic status, and completion. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-permission-node@0.7.29 + ## 0.7.2-next.1 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index f188b4aa51..0634c30de1 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.7.3-next.1", + "version": "0.7.3", "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 3ab27aa3bf..70136051d0 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/backend-common +## 0.22.0 + +### Minor Changes + +- ed83f85: Internal refactor of the database code. + + **BREAKING**: The helper functions `createDatabaseClient` and `ensureDatabaseExists` have been removed from the public interface, since they have no usage within the repository and never were suitable for calling from the outside. Please consider using `coreServices.database` or `DatabaseManager` directly wherever possible instead. + +### Patch Changes + +- 2cc750d: Added `HarnessURLReader` with `readUrl` support. +- 57f692e: Preparing for a stable new backend system release, we are deprecating utilities in the `backend-common` that are not used by the core framework, such as the isomorphic `Git` class. As we will no longer support the isomorphic `Git` utility in the framework packages, we recommend plugins that start maintaining their own implementation of this class. +- 0ec0796: Plugins created through the `legacyPlugin` helper are now able to authenticate requests from plugins that are fully implemented using the new backend system. This fixes the `Key for the ES256 algorithm must be one of type KeyObject or CryptoKey. Received an instance of Uint8Array` error. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- ccc8851: Added config prop `ensureSchemaExists` to support postgres instances where user can create schemas but not databases. +- f66bbb4: Only create a single actual connection to memcache/redis, even in cases where many `CacheService` instances are made +- ba0b8b4: Added option to `ServerTokenManager.fromConfig` that allows it to be instantiated in production without any configured keys. +- Updated dependencies + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/integration@1.11.0 + ## 0.22.0-next.2 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 52ae56cff4..8e0d388352 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.22.0-next.2", + "version": "0.22.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 11a9e78a18..b43a592ba3 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-defaults +## 0.2.18 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-events-node@0.3.4 + ## 0.2.18-next.2 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index ec26ec6471..b5da7abbcf 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.2.18-next.2", + "version": "0.2.18", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index a0f08195b1..ad620ee060 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/backend-dynamic-feature-service +## 0.2.10 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- b611fd0: Updates the `scanRoot` method in the `PluginScanner` class to specifically ignore the `lost+found` directory, which is a system-generated directory used for file recovery on Unix-like systems. Skipping this directory avoids unnecessary errors. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-app-node@0.1.18 + - @backstage/plugin-events-backend@0.3.5 + - @backstage/plugin-permission-node@0.7.29 + ## 0.2.10-next.2 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 715ca5f44d..4c7a7bbaf5 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.10-next.2", + "version": "0.2.10", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index c4a59512e5..9a5a720491 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,45 @@ # example-backend-legacy +## 0.2.98 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1 + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-scaffolder-backend@1.22.6 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.1.24 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-search-backend-module-pg@0.5.27 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-app-backend@0.3.66 + - @backstage/plugin-devtools-backend@0.3.4 + - @backstage/plugin-signals-backend@0.1.4 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/plugin-search-backend@1.5.8 + - @backstage/plugin-techdocs-backend@1.10.5 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-search-backend-module-explore@0.1.24 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/integration@1.11.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23 + - example-app@0.2.97 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16 + - @backstage/plugin-events-backend@0.3.5 + - @backstage/plugin-kubernetes-backend@0.17.1 + - @backstage/plugin-permission-backend@0.5.42 + - @backstage/plugin-permission-node@0.7.29 + - @backstage/plugin-proxy-backend@0.4.16 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35 + - @backstage/plugin-signals-node@0.1.4 + ## 0.2.98-next.2 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index 799387f5d2..d5a3065df5 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.98-next.2", + "version": "0.2.98", "backstage": { "role": "backend" }, diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 3978b5dfd0..d3d8e4bdf8 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-openapi-utils +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + ## 0.1.11-next.1 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index f77626001d..badda72b04 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.11-next.1", + "version": "0.1.11", "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 e6a0d5c278..494634a9de 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-plugin-api +## 0.6.18 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 1fedf0c: Removed explicit `toString()` method from `ServiceRef` type. +- Updated dependencies + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-node@0.4.13 + ## 0.6.18-next.1 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index ca954e4c56..3199c97bda 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.6.18-next.1", + "version": "0.6.18", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index f90d8ff7ef..629f3e7853 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-tasks +## 0.5.23 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + ## 0.5.23-next.1 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index a7f64b9db1..c0331c5e4c 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.23-next.1", + "version": "0.5.23", "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 aafed2071e..d20d983fab 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/backend-test-utils +## 0.3.8 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-app-api@0.7.3 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + ## 0.3.8-next.2 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index f9d591e470..cb7711adfd 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.3.8-next.2", + "version": "0.3.8", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 29e3aecc7c..a0e705f22f 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,41 @@ # example-backend +## 0.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.22.6 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.1.24 + - @backstage/plugin-notifications-backend@0.2.1 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-app-backend@0.3.66 + - @backstage/plugin-devtools-backend@0.3.4 + - @backstage/plugin-signals-backend@0.1.4 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/plugin-search-backend@1.5.8 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15 + - @backstage/plugin-techdocs-backend@1.10.5 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4 + - @backstage/backend-defaults@0.2.18 + - @backstage/plugin-search-backend-module-explore@0.1.24 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-search-backend-module-techdocs@0.1.23 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16 + - @backstage/plugin-kubernetes-backend@0.17.1 + - @backstage/plugin-permission-backend@0.5.42 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15 + - @backstage/plugin-permission-node@0.7.29 + - @backstage/plugin-proxy-backend@0.4.16 + ## 0.0.26-next.1 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 8d59d52ea8..a373a142a0 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.26-next.1", + "version": "0.0.26", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index ef3ca6588e..da7b16dce1 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/catalog-client +## 1.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + ## 1.6.5-next.0 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 39d8227fa5..c9e7f25723 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.6.5-next.0", + "version": "1.6.5", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index c04ac72136..f47465a03e 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/catalog-model +## 1.5.0 + +### Minor Changes + +- 79025f3: Introduce a domain attribute to the domain entity to allow a hierarchy of domains to exist. + ## 1.5.0-next.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 5622a20941..5a60531cb2 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "1.5.0-next.0", + "version": "1.5.0", "description": "Types and validators that help describe the model of a Backstage Catalog", "backstage": { "role": "common-library" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 2b784d7093..7f59e4ef73 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/cli +## 0.26.5 + +### Patch Changes + +- b8f1fc2: The `build-workspace` command no longer manually runs `yarn postpack`, relying instead on the fact that running `yarn pack` will automatically invoke the `postpack` script. No action is necessary if you are running the latest version of yarn 1, 3, or 4. +- fcd3462: Both the target and types library have been bumped from ES2021 to ES2022 in `@backstage/cli/config/tsconfig.json`. +- 0cc5ed3: Add support for `versions:migrate` to do code changes. Can be skipped with `--no-code-changes` +- f97ad04: Add preserveModules to rollup, which allows better async loading and tree-shaking in webpack +- 2a6f10d: The `versions:bump` command will no longer exit with a non-zero status if the version bump fails due to forbidden duplicate package installations. It will now also provide more information about how to troubleshoot such an error. The set of forbidden duplicates has also been expanded to include all `@backstage/*-app-api` packages. +- c5d7b40: Allow passing a `--require` argument through to the Node process during `package start` +- cc3c518: Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows +- 812dff0: Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`. +- f185603: Fixed the dynamic import of vite. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.11.0 + ## 0.26.5-next.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index d6fe116248..f3093445ab 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.26.5-next.1", + "version": "0.26.5", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index f21eed47d0..f171c222b7 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core-app-api +## 1.12.5 + +### Patch Changes + +- 1bed9a3: The Backstage identity session expiration check will no longer fall back to using the provider expiration. This was introduced to smooth out the rollout of Backstage release 1.18, and is no longer needed. + ## 1.12.4 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index b61a860298..0a74dac3f8 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.12.4", + "version": "1.12.5", "publishConfig": { "access": "public" }, diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 3e3f824816..67084d7926 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/core-compat-api +## 0.2.5 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.5 + ## 0.2.5-next.1 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index fbea128a68..9124060464 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.5-next.1", + "version": "0.2.5", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index dc31cb9f4d..4c2a973813 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/core-components +## 0.14.7 + +### Patch Changes + +- e42779e: Properly log the `errorInfo` in `ErrorBoundary` +- 5223c4c: Fixed an internal circular import that broke Jest mocks. +- 71e4229: Added optional `initialShowDropDown` prop to `SidebarSubmenuItem` to internally manage the initial display state of the dropdown items. +- a2ee4df: Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines. + Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card. +- 5b7b49b: The `SignInPage` guest provider will now fall back to legacy guest auth if the backend request fails, allowing guest auth without a running backend. +- 359376a: Removing react-text-truncate with css styles. +- Updated dependencies + - @backstage/theme@0.5.4 + ## 0.14.7-next.2 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 316ce46ac4..435a013062 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.7-next.2", + "version": "0.14.7", "publishConfig": { "access": "public" }, diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index ee49b7d8f5..be4c46ab03 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/create-app +## 0.5.15 + +### Patch Changes + +- c066c88: Removed `packages/backend/src/types.ts` from the template as it is unused. It was mistakenly left in after moving the template to the new backend system. +- 5d99272: Update local development dependencies. +- 0478509: Bumped create-app version. +- d85dd88: Bumped create-app version. +- 8105aad: Removed the Tech Radar and GitHub Actions plugins from the template, which have been moved to the community plugins repository. + ## 0.5.15-next.2 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index ec0e764fff..422442ed2b 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.15-next.2", + "version": "0.5.15", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 673ee43d24..c8e32a39d0 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/dev-utils +## 1.0.32 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/app-defaults@1.5.5 + - @backstage/integration-react@1.1.27 + ## 1.0.32-next.2 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index f6308b6446..de5acd3249 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.32-next.2", + "version": "1.0.32", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 326002b350..7ff6862d2f 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,12 @@ # e2e-test +## 0.2.16 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.15 + ## 0.2.16-next.1 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index e4ddf47d73..a77bb8c8a1 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.16-next.1", + "version": "0.2.16", "private": true, "backstage": { "role": "cli" diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 60dde12184..63a7650400 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/eslint-plugin +## 0.1.8 + +### Patch Changes + +- 65ec043: add some `pickers` fixes + ## 0.1.7 ### Patch Changes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 395a3eea64..0d8db000e0 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/eslint-plugin", - "version": "0.1.7", + "version": "0.1.8", "description": "Backstage ESLint plugin", "publishConfig": { "access": "public" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index c560948cc2..b9571091a8 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/frontend-app-api +## 0.7.0 + +### Minor Changes + +- ddddecb: Extensions in app-config now always affect ordering. Previously, only when enabling disabled extensions did they rise to the top. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/frontend-plugin-api@0.6.5 + ## 0.7.0-next.2 ### Minor Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 7602f688ef..2e350b5a98 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.7.0-next.2", + "version": "0.7.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 1c681fa6bc..f12de020f5 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/frontend-plugin-api +## 0.6.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + ## 0.6.5-next.1 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 94906cfc67..2dedd6fc12 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.5-next.1", + "version": "0.6.5", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 0d41932eb0..b2abbd9c2d 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/frontend-test-utils +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/test-utils@1.5.5 + ## 0.1.7-next.2 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 44eddc2ada..4845e7e9b1 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.1.7-next.2", + "version": "0.1.7", "backstage": { "role": "web-library" }, diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 33de746f92..f44e72919c 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/integration-react +## 1.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.11.0 + ## 1.1.27-next.0 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 5d87ccaa13..07effadbae 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.1.27-next.0", + "version": "1.1.27", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 0ed0080b10..0c00b80d58 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/integration +## 1.11.0 + +### Minor Changes + +- 2cc750d: Added `HarnessIntegration` via the `ScmIntegrations` interface. + ## 1.11.0-next.0 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index ad41e216a7..da4b700fd6 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.11.0-next.0", + "version": "1.11.0", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index b3aefc9e67..251ddb28de 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/repo-tools +## 0.9.0 + +### Minor Changes + +- 683870a: Adds 2 new commands `repo schema openapi diff` and `package schema openapi diff`. `repo schema openapi diff` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes. They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. + +### Patch Changes + +- 9ae9bb2: Update the paths logic in the api reports command to support complex subpaths +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + ## 0.9.0-next.2 ### Minor Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 41492f2366..5c2e5e859c 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.9.0-next.2", + "version": "0.9.0", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index cd27b1c1a7..0bb564782c 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,22 @@ # techdocs-cli-embedded-app +## 0.2.96 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.20.0 + - @backstage/cli@0.26.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/app-defaults@1.5.5 + - @backstage/integration-react@1.1.27 + - @backstage/test-utils@1.5.5 + - @backstage/plugin-techdocs-react@1.2.4 + ## 0.2.96-next.2 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 687a469914..c6f776015c 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.96-next.2", + "version": "0.2.96", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 3b2c544e7d..c1c15bb78c 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,15 @@ # @techdocs/cli +## 1.8.11 + +### Patch Changes + +- 1a0e009: Fix cookie endpoint mock for `serve` +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-techdocs-node@1.12.4 + ## 1.8.11-next.1 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 13eed9d249..5af004aeb0 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.11-next.1", + "version": "1.8.11", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index d28d0cac49..4c8e0c1173 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/test-utils +## 1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + ## 1.5.5-next.0 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 2e46e3a9b0..d48b652c43 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.5.5-next.0", + "version": "1.5.5", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 20703a1d9d..397b6c80f5 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.5.4 + +### Patch Changes + +- f1462df: Fixed bug where scrollbars don't pick up the theme when in dark mode + ## 0.5.4-next.0 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index 07eefe756f..698d4f567c 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.5.4-next.0", + "version": "0.5.4", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 24b30e2fda..92eb1b44f1 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-api-docs +## 0.11.5 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- 725ff0b: Fix dark mode text color inside tables in `description:` from OpenAPI definitions +- Updated dependencies + - @backstage/plugin-catalog@1.20.0 + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.11.5-next.2 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 4b8ff68d86..398407e33f 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.5-next.2", + "version": "0.11.5", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin" diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 2b51f92102..c33d774b1a 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-app-backend +## 0.3.66 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-app-node@0.1.18 + ## 0.3.66-next.1 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index fc4a6a8ded..d0b07f4041 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.66-next.1", + "version": "0.3.66", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index dfc8e08cb9..75fc04fbdf 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-app-node +## 0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + ## 0.1.18-next.1 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index a38ed25171..cbaca9f3d1 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-node", "description": "Node.js library for the app plugin", - "version": "0.1.18-next.1", + "version": "0.1.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index c3e117a8a0..bf39546c90 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-visualizer +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/frontend-plugin-api@0.6.5 + ## 0.1.6-next.1 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 0b2370054c..a3e9d89e3b 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.6-next.1", + "version": "0.1.6", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin" diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index 915c9b0f19..f75da3056e 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index 0f0c3eeda9..0b0f949c44 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", "description": "The atlassian-provider backend module for the auth plugin.", - "version": "0.1.10-next.1", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index 784334ac96..f3c1594df9 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.10 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.10-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 86d4793d81..e699c13dfd 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", "description": "The aws-alb provider module for the Backstage auth backend.", - "version": "0.1.10-next.2", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 0ed21e2749..901f88b20a 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index e936f48f43..bec9f86224 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.1.1-next.1", + "version": "0.1.1", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index 4b414015cc..a3787cfdc0 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index ba1184ab0f..3d16dc4bce 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.1.1-next.1", + "version": "0.1.1", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index 1aabb47fb9..d8e5dfe4d5 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index 77f77052b5..7fe161480b 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.1.1-next.1", + "version": "0.1.1", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index 03576e316e..89f6dd9805 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.2.13-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 1492ebaaf2..603fcc64c7 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", "description": "A GCP IAP auth provider module for the Backstage auth backend", - "version": "0.2.13-next.1", + "version": "0.2.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 32ea1205d9..a892924c64 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.1.15 + +### Patch Changes + +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.15-next.2 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 3f587ad033..2a45c2ed78 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.1.15-next.2", + "version": "0.1.15", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 1f4f5ecbf9..201fa5901b 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.15-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 5d8ee28020..146c26fffd 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", "description": "The gitlab-provider backend module for the auth plugin.", - "version": "0.1.15-next.1", + "version": "0.1.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 9e7e4043a3..46ea104253 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.15-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index a9b529504d..35814282ec 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", "description": "A Google auth provider module for the Backstage auth backend", - "version": "0.1.15-next.1", + "version": "0.1.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 56c4e179b8..91565c88fd 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.1.4 + +### Patch Changes + +- 07d8cca: Error if used outside of a development environment without explicit allowance +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 9676afb176..e27c19eb5d 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.1.4-next.1", + "version": "0.1.4", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index 14e4cef987..83c3bb229d 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.13-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index a8569903cc..9e2719acc3 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", "description": "The microsoft-provider backend module for the auth plugin.", - "version": "0.1.13-next.1", + "version": "0.1.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 747d8fa317..dbb970e028 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.15-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 7cbc5929ae..6aecfd6c48 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", "description": "The oauth2-provider backend module for the auth plugin.", - "version": "0.1.15-next.1", + "version": "0.1.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index acc41221f6..e5366af8b6 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.11-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index cbcef196a5..0f3063db34 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", "description": "The oauth2-proxy-provider backend module for the auth plugin.", - "version": "0.1.11-next.1", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 6713b3c7b6..e8716f0ce1 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.1.9 + +### Patch Changes + +- dd53bf3: Add nonce to authorize request to be added in ID token +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend@0.22.5 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.9-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index c8894fc070..34bb282ba5 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", "description": "The oidc-provider backend module for the auth plugin.", - "version": "0.1.9-next.1", + "version": "0.1.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 43e434a711..8378a83e7c 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.0.11-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 3006544145..b8a679b0b0 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", "description": "The okta-provider backend module for the auth plugin.", - "version": "0.0.11-next.1", + "version": "0.0.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index a115289d0b..e9631b54cc 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.12-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 163c3f8e05..227055d366 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", "description": "The pinniped-provider backend module for the auth plugin.", - "version": "0.1.12-next.1", + "version": "0.1.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 9245b6ded5..97753a12d2 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index 6054f63ea1..64af928848 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.1.10-next.1", + "version": "0.1.10", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 59a7aa4af0..2b161d638a 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-auth-backend +## 0.22.5 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 4a0577e: fix: Move config declarations to appropriate auth backend modules +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.9 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15 + - @backstage/plugin-auth-backend-module-google-provider@0.1.15 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.11 + ## 0.22.5-next.2 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 910c7d4180..0797145855 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.5-next.2", + "version": "0.22.5", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin" diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index af4318227d..ecfd0c78b6 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-node +## 0.4.13 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + ## 0.4.13-next.1 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index ba05caaad4..8b0e7f87c3 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.4.13-next.1", + "version": "0.4.13", "backstage": { "role": "node-library" }, diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index ebd567184f..46ddcc9a51 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-react +## 0.1.2 + +### Patch Changes + +- c297afd: When using `CookieAuthRefreshProvider` or `useCookieAuthRefresh`, a 404 response from the cookie endpoint will now be treated as if cookie auth is disabled and is not needed. +- Updated dependencies + - @backstage/core-components@0.14.7 + ## 0.1.2-next.1 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index c223b4c40f..7b85295f48 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.2-next.1", + "version": "0.1.2", "description": "Web library for the auth plugin", "backstage": { "role": "web-library" diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 1397e67af4..b20502394f 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.19 + +### Patch Changes + +- d76cb29: Updated dependency `ts-morph` to `^22.0.0`. +- Updated dependencies + - @backstage/integration@1.11.0 + ## 0.2.19-next.0 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 8d3458315e..a6d52a1850 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.2.19-next.0", + "version": "0.2.19", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library" diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index f0e0b8e6aa..cf7dc39048 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.3.13-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index d80383a18f..ba9a299fad 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.3.13-next.2", + "version": "0.3.13", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 6d3af364bb..649c15872a 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.38 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.1.38-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index a0a7edb728..e5b47db18c 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.1.38-next.2", + "version": "0.1.38", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index fc48605090..2d63fdeadd 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.2.1 + +### Patch Changes + +- f3f0281: Fix incorrect dependency import. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/backend-openapi-utils@0.1.11 + ## 0.2.1-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 408fdfcc4f..af9a299848 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.2.1-next.2", + "version": "0.2.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 8884d911d3..1fa58406c4 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-bitbucket-cloud-common@0.2.19 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.2.5-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index a6554c173a..b821a1e01d 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.2.5-next.2", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 424f143da1..ffb79454cf 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.32 + +### Patch Changes + +- 062ffb1: Allow skipping archived repositories (`skipArchivedRepos` flag) on Bitbucket. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + ## 0.1.32-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index e1b0e4c362..684b5fa95a 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.32-next.2", + "version": "0.1.32", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 095366892f..008b18882b 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.1.19-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 758a909b3c..f0f3b1722f 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.1.19-next.2", + "version": "0.1.19", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 3aaa06ae02..2b5f22e034 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.35 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/integration@1.11.0 + ## 0.1.35-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index f58d6bb85f..1e9ff4284c 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.35-next.2", + "version": "0.1.35", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 01a6fb3b7a..99f073203c 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.13 + +### Patch Changes + +- 5bdeaa7: Fixed an issue where the `catalog-backend-module-github-org` would not correctly create groups using `default` as namespace in case a single organization was configured. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-catalog-backend-module-github@0.6.1 + - @backstage/plugin-events-node@0.3.4 + ## 0.1.13-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 4557cbedef..9f73d2a052 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.1.13-next.2", + "version": "0.1.13", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index ad4281d185..09735e37ca 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.1 + +### Patch Changes + +- 0b50143: GitHub push events now schedule a refresh on entities that have a `refresh_key` matching the `catalogPath` config itself. + This allows to support a `catalogPath` configuration that uses glob patterns. +- f2a2a83: Updated to use the new `catalogAnalysisExtensionPoint` API. +- 5bdeaa7: Added `alwaysUseDefaultNamespace` option to `GithubMultiOrgEntityProvider`. + + If set to true, the provider will use `default` as the namespace for all group entities. Groups with the same name across different orgs will be considered the same group. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.6.1-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 05306549c3..d71303629a 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.6.1-next.2", + "version": "0.6.1", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 4926c5efc4..793b10fd8b 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.0.1 + +### Patch Changes + +- a70377d: Added a new `catalog-backend-module-gitlab-org` module which adds the `GitlabOrgDiscoveryEntityProvider` to the catalog's providers using the new backend system. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + ## 0.0.1-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index e0eb268ba6..d0069fc968 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.0.1-next.2", + "version": "0.0.1", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 661d422547..5500adc6b1 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.15 + +### Patch Changes + +- a70377d: Added events support for `GitlabDiscoveryEntityProvider` and `GitlabOrgDiscoveryEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.3.15-next.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index c485326f3e..83e2a47be9 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -72,5 +72,5 @@ ] } }, - "version": "0.3.15-next.4" + "version": "0.3.15" } diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 961ebe081c..b20c236f55 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.23 + +### Patch Changes + +- 8c1ab9e: Fix plugin/incremental-ingestion 'Maximum call stack size exceeded' error when ingest large entities. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + ## 0.4.23-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 0a4f73c5d4..8f6016a228 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.4.23-next.2", + "version": "0.4.23", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 22da3d6099..917f895046 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.34 + +### Patch Changes + +- 7699967: Remove dependency to Winston Logger and use Backstage LoggerService instead +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.5.34-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 2be1113e64..2b447ea7ec 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.5.34-next.2", + "version": "0.5.34", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 4d6a4bc350..526a8609ac 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.26 + +### Patch Changes + +- 49eab29: Fixed disabling of user photo fetching. Previously, the config value wasn't propagated properly, so user photos was still being fetched despite disabled by config. +- 6e370e6: Handle fetching huge amounts of users from Azure without crashing +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.5.26-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index ddc3ca4af3..b12afe2f01 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.5.26-next.2", + "version": "0.5.26", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 2b1e0650a8..a0f8b3945d 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-catalog-backend@1.22.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.1.36-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 2eb832684d..40975fc254 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.1.36-next.2", + "version": "0.1.36", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 84d705b8be..e49b8fdfa2 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + ## 0.1.24-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 6d3a71092e..d12990b54d 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.1.24-next.2", + "version": "0.1.24", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index b51aacb4f1..e2a2e7f299 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.1.16-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 2bf29a3bb3..5f7a22b8dc 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.1.16-next.2", + "version": "0.1.16", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 1a2e2a2785..16b105df8d 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.5 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- 6f5a3a3: Correctly convert owner to string in case owner has not been provided +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.4.5-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index ba13df5a2a..c1df39fc6c 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.4.5-next.2", + "version": "0.4.5", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index cb4e25d190..3934bb495f 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-backend +## 1.22.0 + +### Minor Changes + +- f2a2a83: Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`. +- f2a2a83: The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API. +- 8d14475: Emit well known relationships for the Domain entity kind. + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- c6cb568: Add lifecycle monitoring for the catalog processing +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 8479a0b: Fixed bug in stitching queue gauge that included entities that are scheduled in the future. +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-search-backend-module-catalog@0.1.24 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + - @backstage/integration@1.11.0 + - @backstage/backend-openapi-utils@0.1.11 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-node@0.7.29 + ## 1.22.0-next.2 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index f57efe5ae6..44f35dccf2 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.22.0-next.2", + "version": "1.22.0", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin" diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index f070ca11e2..a6dc3f06d6 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-common +## 1.0.23 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + ## 1.0.23-next.0 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 59d0019512..936fc3c935 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-common", - "version": "1.0.23-next.0", + "version": "1.0.23", "description": "Common functionalities for the catalog plugin", "backstage": { "role": "common-library" diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 372a94a19c..9d5a638ad7 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-graph +## 0.4.5 + +### Patch Changes + +- 39564b3: Allow multiple edges with different type (e.g. `ownedBy` and `applicationOwnerBy`) to have the same source and target node. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + ## 0.4.5-next.2 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index ac9039a441..4d2ac4b393 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.5-next.2", + "version": "0.4.5", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index f201bf7552..8bc9f0914e 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-import +## 0.11.0 + +### Minor Changes + +- e1174b0: `EntityListComponent` uses `entityPresentationApi` instead of `humanizeEntityRef` to display Entity + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.10.11-next.2 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index be4122b105..822ed6f395 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.10.11-next.2", + "version": "0.11.0", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 949a44b384..8948ad6f98 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-node +## 1.12.0 + +### Minor Changes + +- f2a2a83: Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`. +- f2a2a83: Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-node@0.7.29 + ## 1.12.0-next.2 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 010389079d..da6a67f163 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.12.0-next.2", + "version": "1.12.0", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library" diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index bb2477302e..ab6f2290ba 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-react +## 1.12.0 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + ## 1.12.0-next.2 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 85b7ba95be..6f0094f0c7 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.12.0-next.2", + "version": "1.12.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 06d4344889..989a852b55 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index eb35eae31e..059dc8802b 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.2.4-next.1", + "version": "0.2.4", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index abfcf1012c..b2d036e914 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog +## 1.20.0 + +### Minor Changes + +- 8834daf: Updated the presentation API to return a promise, in addition to the snapshot and observable that were there before. This makes it much easier to consume the API in a non-React context. + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- 5d99272: Update local development dependencies. +- 4118530: Avoiding pre-loading display total count undefined for table counts +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-search-react@1.7.11 + ## 1.20.0-next.2 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index e32191c629..bad4466146 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.20.0-next.2", + "version": "1.20.0", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 792691f33d..4d6609477c 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-config-schema +## 0.1.55 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + ## 0.1.55-next.1 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 19f2401602..bbf1e786a1 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.55-next.1", + "version": "0.1.55", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin" diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 38e0da331f..902a43c880 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-devtools-backend +## 0.3.4 + +### Patch Changes + +- 036feca: Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-permission-node@0.7.29 + ## 0.3.4-next.2 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index fd2c8eac4a..00d8df80ae 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.4-next.2", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index b618417ce0..ee7fd659bb 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-devtools +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/frontend-plugin-api@0.6.5 + ## 0.1.14-next.1 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 64b3626546..c711d941c6 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.14-next.1", + "version": "0.1.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 498f89b6fa..1398d95e4a 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.3.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-events-node@0.3.4 + ## 0.3.4-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index ab33e2c451..0e889a2f8e 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.3.4-next.1", + "version": "0.3.4", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index 1fbb360beb..d9c8e91140 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index f7ade8b3a2..9264fe2374 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-azure", - "version": "0.2.4-next.1", + "version": "0.2.4", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index b649c38b5b..b2734012c0 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index baed6171fd..5dc4d0147a 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-cloud", - "version": "0.2.4-next.1", + "version": "0.2.4", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index 57c6390296..5a1134e692 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index b3f8161604..61d0fafd53 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gerrit", - "version": "0.2.4-next.1", + "version": "0.2.4", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index ae5ee59263..88b0b1634d 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-github +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 9fb876f714..d234a7e01b 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.2.4-next.1", + "version": "0.2.4", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 73c4c7e281..b6bb80f18f 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index e10b26736f..f793455a5d 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.2.4-next.1", + "version": "0.2.4", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index f5ba8b9609..eeb52a1d29 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4 + ## 0.1.28-next.1 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index f766130efc..b96b7afe7f 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.28-next.1", + "version": "0.1.28", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library" diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 1bb33b6db1..0862263896 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + ## 0.3.5-next.1 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index dc36a33d07..1cd3f909cc 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.5-next.1", + "version": "0.3.5", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 7238c9ed49..5b907547d8 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-node +## 0.3.4 + +### Patch Changes + +- 7e5a50d: added `eventsServiceFactory` to `defaultServiceFactories` to resolve issue where different instances of the EventsServices could be used +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + ## 0.3.4-next.2 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 9500fdc0ad..c082da2eab 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.3.4-next.2", + "version": "0.3.4", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library" diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 2d6dee39e2..d152236a11 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/plugin-todo-list-backend +## 1.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 1.0.27-next.1 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index cd45af6d37..15a9d16ee9 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.27-next.1", + "version": "1.0.27", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 0962426ac3..a86ffe7c60 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list +## 1.0.27 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + ## 1.0.27-next.1 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 849b96433a..4bd01f6e32 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.27-next.1", + "version": "1.0.27", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index b1f8f6793e..6013cad734 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-home-react +## 0.1.13 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-components@0.14.7 + ## 0.1.13-next.1 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index bb04ce8211..3853ef0f85 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.13-next.1", + "version": "0.1.13", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library" diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 6fb3fbaec9..9543096cd0 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-home +## 0.7.4 + +### Patch Changes + +- 2196d3e: Use relative time when displaying visits from the same day +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/plugin-home-react@0.1.13 + - @backstage/core-app-api@1.12.5 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + ## 0.7.4-next.2 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 63f01e84c4..79003a2ad7 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.4-next.2", + "version": "0.7.4", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 48e205f868..bc447b62e0 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes-backend +## 0.17.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-kubernetes-common@0.7.6 + - @backstage/plugin-kubernetes-node@0.1.12 + - @backstage/plugin-permission-node@0.7.29 + ## 0.17.1-next.2 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 24a52bc8a8..7589a453a7 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.17.1-next.2", + "version": "0.17.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 09adcabc63..aad774818d 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/plugin-kubernetes-react@0.3.5 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.0.11-next.2 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 409c385263..db9d08b5d7 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.11-next.2", + "version": "0.0.11", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 55f3485ff4..778364af1b 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-kubernetes-common +## 0.7.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + ## 0.7.6-next.0 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 77201e4fbf..f3bba8b855 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.7.6-next.0", + "version": "0.7.6", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 89c7a58526..730d43b95c 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes-node +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.1.12-next.1 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 79e304671a..dbe0a35d44 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.12-next.1", + "version": "0.1.12", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library" diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index e05d103ad6..76abb6a50a 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes-react +## 0.3.5 + +### Patch Changes + +- 3102a99: add the namespace label to CronJobDrawer & IngressDrawer. +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.3.5-next.1 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 7ff42ab26c..0991845ebd 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-react", "description": "Web library for the kubernetes-react plugin", - "version": "0.3.5-next.1", + "version": "0.3.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 87b1132710..abd18e5e21 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-kubernetes +## 0.11.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/plugin-kubernetes-react@0.3.5 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.11.10-next.2 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index bfb8ed1e2c..fd02de4137 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.10-next.2", + "version": "0.11.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 1c6e150a29..41bb1d2d9e 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-notifications-backend-module-email +## 0.0.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- e538b10: Support relative links in notifications sent via email +- dbf2696: Allow sending notifications by email with the new notifications module +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-notifications-node@0.1.4 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + ## 0.0.1-next.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 1884dc6352..892c8c9156 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.0.1-next.1", + "version": "0.0.1", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 5d6792659d..26762f5b2a 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-notifications-backend +## 0.2.1 + +### Patch Changes + +- d541ff6: Fixed email processor `esm` issue and config reading +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- cba628a: Add possibility to generate random notifications on the fly in local development +- ee09dfc: Updated documentation for sending messages by external services. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/plugin-notifications-node@0.1.4 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-signals-node@0.1.4 + ## 0.2.1-next.2 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 3aada48a9c..6e6c0e04cb 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.2.1-next.2", + "version": "0.2.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 2b6321a3f8..86813090da 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-notifications-node +## 0.1.4 + +### Patch Changes + +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-signals-node@0.1.4 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 1abfcc07ba..9f764c732b 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.1.4-next.1", + "version": "0.1.4", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library" diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 13cff2c7f7..278ac77062 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-notifications +## 0.2.1 + +### Patch Changes + +- e6bf85f: Allow overriding `NotificationsPage` page properties +- f730c0b: The user can newly mark all unread messages as read at one click. +- bfcb2f1: Allow showing notifications as snackbars in the UI +- e49a810: Show all notifications by default to match the sidebar item status +- 42eaf63: Increase default and allow modifying notification snackbar auto hide duration +- a42a19b: Empty descriptions are not rendered to improve the look&feel. +- 1bc3b86: Fix to show web notifications even when browser is on foreground. Fix duplicate notifications with multiple tabs. +- f793112: Allow defining `className` and additional properties for `NotificationsSideBarItem` +- e1c7d6e: Fix infinite loop in the notification title counter +- fcda449: The rendered size of a notification is limited for very long descriptions. +- f6633ca: Add option to set the notification as read automatically when the notification link is opened +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + ## 0.2.1-next.2 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 09127ecf04..8307103942 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.1-next.2", + "version": "0.2.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 8767b0e303..853a4e76e3 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-org-react +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/catalog-client@1.6.5 + ## 0.1.24-next.2 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 53f8f83fe2..dd9bc64f5a 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.24-next.2", + "version": "0.1.24", "backstage": { "role": "web-library" }, diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index a18e9a4e61..885f03cb3d 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-org +## 0.6.25 + +### Patch Changes + +- 99e6105: Fix ownership card sometimes locking up for complex org structures +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.6.25-next.2 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 719eef72ce..5c9580ee0b 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.25-next.2", + "version": "0.6.25", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin" diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 90874346ad..d2ae73f5f4 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-permission-node@0.7.29 + ## 0.1.15-next.1 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index ef5dfade7f..6e08904940 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.1.15-next.1", + "version": "0.1.15", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index cc5b497a2c..6548fdfac1 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-backend +## 0.5.42 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-permission-node@0.7.29 + ## 0.5.42-next.1 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 3671e2b5a4..e039cd1898 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.42-next.1", + "version": "0.5.42", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index fd84633615..8b145de147 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-node +## 0.7.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.7.29-next.1 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index c510014089..bf6afc9e34 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.29-next.1", + "version": "0.7.29", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index b133154c85..80e2f2e241 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.4.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + ## 0.4.16-next.1 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 9430f7120d..0614956a18 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.4.16-next.1", + "version": "0.4.16", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index cdbe8cd77e..c2418d6309 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.1.10-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index d9d648ec6d..bc6b4a45d0 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.1.10-next.2", + "version": "0.1.10", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 93fc9f821f..9a1340c1ad 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.1.8-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index d58a871f99..abde43fb2f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.1.8-next.2", + "version": "0.1.8", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 9b083cfaf4..fc245e48ba 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.8 + +### Patch Changes + +- 24dd655: Add examples for `publish:bitbucketServer:pull-request` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.1.8-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index b75099e81d..2a9fcc3f9d 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.1.8-next.2", + "version": "0.1.8", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 5067f00aa8..97a57ff28f 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8 + - @backstage/integration@1.11.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8 + ## 0.2.8-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index ce82b551b4..355ca41844 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.2.8-next.2", + "version": "0.2.8", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 23a7973822..77152b9659 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.2.19-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index f33e71b536..7cca3be249 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.19-next.2", + "version": "0.2.19", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 7220f00aa1..a6c6b24a07 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.42 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.2.42-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 78b9a2a8ce..b0f137ffab 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.42-next.2", + "version": "0.2.42", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index fd11a0f755..3daf8fc28a 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.1.10-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 7215239608..1d9e003cfd 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.1.10-next.2", + "version": "0.1.10", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index c88d71f652..6ec5592bc5 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.8 + +### Patch Changes + +- 554af73: Allow defining `repoVisibility` field for the action `publish:gitea` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.1.8-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index c49954b6b9..1d8ad190bc 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.8-next.2", + "version": "0.1.8", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 39454530eb..40c8349399 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.2.8 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- 52ab241: Adding support to change the default commit author for `publish:github:pull-request` +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.2.8-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index a593541e08..56dbd291a3 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.2.8-next.2", + "version": "0.2.8", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 18efb70009..829e2122f3 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.0 + +### Minor Changes + +- 18f736f: Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests + +### Patch Changes + +- 8fa8a00: Add merge method and squash option for project creation +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- ffc73ec: Add examples for `gitlab:repo:push` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.4.0-next.2 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index d03fb65c1b..383ef0e79d 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.4.0-next.2", + "version": "0.4.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 9a1c1b8891..d5d3902191 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.0.1 + +### Patch Changes + +- 503d769: Add a new scaffolder action to allow sending notifications from templates +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/plugin-notifications-node@0.1.4 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + ## 0.0.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 7cb996f72b..07cb5e9bb1 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.0.1-next.0", + "version": "0.0.1", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index f721032c0a..64c1c79d7c 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.35 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/integration@1.11.0 + ## 0.4.35-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index f8e1289359..0d822764dd 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.35-next.2", + "version": "0.4.35", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index d3685153d5..fa5b12ae0e 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + ## 0.1.26-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index bd7555a8c2..1f2e48ad59 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.26-next.1", + "version": "0.1.26", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 96b7222d0f..cb1a73f9f7 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.3.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/plugin-scaffolder-node-test-utils@0.1.4 + ## 0.3.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index c0f0c85f72..9a2fb1fdb1 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.3.1-next.1", + "version": "0.3.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 265517070d..05be28c1ae 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend +## 1.22.6 + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- 025641b: Fix issue with the log format not being respected when logging from actions +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- e4b50ab: Scaffolder workspace serialization +- 025641b: Redact `meta` fields too with the logger +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-scaffolder-node@0.4.4 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16 + - @backstage/plugin-permission-node@0.7.29 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10 + ## 1.22.6-next.2 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index cf3d3d1ca0..f2f4da6a84 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.22.6-next.2", + "version": "1.22.6", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 9011c681cb..8b5c2d7c99 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-common +## 1.5.2 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/catalog-model@1.5.0 + ## 1.5.2-next.1 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 614bd16a25..a0060b1427 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.2-next.1", + "version": "1.5.2", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 5914c8fdee..72804941e4 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-test-utils@0.3.8 + - @backstage/plugin-scaffolder-node@0.4.4 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 99adae5ab7..8686592f7a 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.4-next.1", + "version": "0.1.4", "backstage": { "role": "node-library" }, diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index d9e2804297..5c6fe0f6cc 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-node +## 0.4.4 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- e4b50ab: Scaffolder workspace serialization +- f633efa: To remove the dependency on the soon-to-be-deprecated `backend-common` package, this package now maintains its own isomorphic Git class implementation. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/integration@1.11.0 + ## 0.4.4-next.2 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index c45382a707..af26f204dd 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.4-next.2", + "version": "0.4.4", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library" diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index ca23ab25be..c445b76d06 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder-react +## 1.8.5 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/catalog-client@1.6.5 + ## 1.8.5-next.2 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 728c6e23d7..9187d9259d 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.8.5-next.2", + "version": "1.8.5", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library" diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 6edfc1484d..c4949715d7 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-scaffolder +## 1.20.0 + +### Minor Changes + +- 4268696: `MultiEntityPicker` uses `EntityDisplayName` instead of `humanizeEntityRef` to display entity. + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- 131e5cb: Fix broken links in README. +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- 762141c: Fixed a bug where the `MultiEntityPicker` was not able to be set as required +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-scaffolder-react@1.8.5 + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/integration@1.11.0 + - @backstage/catalog-client@1.6.5 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + ## 1.19.4-next.2 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index fe0deb9036..c11f1a163a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.19.4-next.2", + "version": "1.20.0", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin" diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 6ed9030a22..d0c84a7617 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-search-backend-module-catalog +## 0.1.24 + +### Patch Changes + +- b192752: Updated `README.md` to point to `packages/backend` instead of `packages/backend-next`. +- d5fff66: Fix wiring of the module exported at the `/alpha` path, which was causing authentication failures. +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.1.24-next.2 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index aef1a1028d..f7eca60fbd 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.24-next.2", + "version": "0.1.24", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 1af0204403..5bf9aa19ea 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.4.1 + +### Patch Changes + +- 5252ee1: Fix never resolved indexer promise. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-search-backend-node@1.2.22 + ## 1.4.1-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 85489b4043..128bc0e28c 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.4.1-next.1", + "version": "1.4.1", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index ce6499ab4e..fc8ff4b307 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-explore +## 0.1.24 + +### Patch Changes + +- ca6e2e0: Migrate search collator to use the new auth services. +- 5d99272: Update README.md to point to explore plugin in community-plugins repository. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + ## 0.1.24-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index d181d70cbf..28ba6945c3 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.1.24-next.1", + "version": "0.1.24", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 37386a7fd1..ba6674606d 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.27 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-search-backend-node@1.2.22 + ## 0.5.27-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 3aec50e503..6645e2642f 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.27-next.1", + "version": "0.5.27", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index c8faaf7609..c56c40ebe4 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-search-backend-node@1.2.22 + ## 0.1.11-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index 3c76ed164b..5ae74d37bc 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.1.11-next.1", + "version": "0.1.11", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 850db158c8..569a73c232 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.1.23 + +### Patch Changes + +- 5dc5f4f: Allow the `tokenManager` parameter to be optional when instantiating collator +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.0 + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + - @backstage/plugin-techdocs-node@1.12.4 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.1.23-next.2 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 5cf53c40be..c27fe52d93 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.23-next.2", + "version": "0.1.23", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 854996ba53..74ba4c86c1 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-node +## 1.2.22 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/backend-tasks@0.5.23 + ## 1.2.22-next.1 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index c734681980..a3c9e16f95 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.2.22-next.1", + "version": "1.2.22", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index c4bc71318c..0a80301ede 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search-backend +## 1.5.8 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/repo-tools@0.9.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-search-backend-node@1.2.22 + - @backstage/backend-openapi-utils@0.1.11 + - @backstage/plugin-permission-node@0.7.29 + ## 1.5.8-next.2 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index dde1dc9de9..146e50d177 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.8-next.2", + "version": "1.5.8", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin" diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index c4e75fa41a..046532facf 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-react +## 1.7.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + - @backstage/frontend-plugin-api@0.6.5 + ## 1.7.11-next.1 ### Patch Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 274deae1d7..1e6b2b0e8e 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.7.11-next.1", + "version": "1.7.11", "backstage": { "role": "web-library" }, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 2b221c75e9..b738d853f8 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search +## 1.4.11 + +### Patch Changes + +- 0501243: Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/plugin-search-react@1.7.11 + ## 1.4.11-next.2 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index f064be1433..1a9a09a5fe 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.11-next.2", + "version": "1.4.11", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin" diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index 5287438fb6..f6eeac9bf4 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-signals-backend +## 0.1.4 + +### Patch Changes + +- 845d56a: Improved signal lifecycle management and added server side pinging of connections +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + - @backstage/plugin-signals-node@0.1.4 + ## 0.1.4-next.2 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index b6f16c272a..b148d4d5e5 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.4-next.2", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index ea703d4291..e26991c787 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-signals-node +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-events-node@0.3.4 + - @backstage/plugin-auth-node@0.4.13 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index 970b89aca2..4d1ad9e1ef 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-signals-node", "description": "Node.js library for the signals plugin", - "version": "0.1.4-next.1", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index fdfddfdf2a..971a849a03 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-signals +## 0.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/theme@0.5.4 + ## 0.0.6-next.1 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 7d5fb3bcf9..ec618415a8 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.6-next.1", + "version": "0.0.6", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 00c7297773..8a4b3d1eb8 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.32 + +### Patch Changes + +- 2f13862: Fix bug in TechDocsAddonTester when jest.resetAllMocks is called between tests +- Updated dependencies + - @backstage/plugin-catalog@1.20.0 + - @backstage/plugin-techdocs@1.10.5 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/core-app-api@1.12.5 + - @backstage/integration-react@1.1.27 + - @backstage/test-utils@1.5.5 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-techdocs-react@1.2.4 + ## 1.0.32-next.2 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index e1ee4400bb..02161159e9 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.32-next.2", + "version": "1.0.32", "backstage": { "role": "web-library" }, diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index d6847da1ae..d4d18976b0 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-techdocs-backend +## 1.10.5 + +### Patch Changes + +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-techdocs-node@1.12.4 + - @backstage/integration@1.11.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23 + - @backstage/catalog-client@1.6.5 + - @backstage/plugin-catalog-common@1.0.23 + ## 1.10.5-next.2 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 79a701413a..a5a8e0ba76 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.5-next.2", + "version": "1.10.5", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin" diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 77e83144d5..2ab0cef022 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-techdocs-react@1.2.4 + ## 1.1.10-next.2 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 8c89c9bf37..2aaca2c4e0 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.10-next.2", + "version": "1.1.10", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module" diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index aea92cb71c..59a0cc2055 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-node +## 1.12.4 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/integration@1.11.0 + ## 1.12.4-next.2 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 0eda77c887..7d4c83545d 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.4-next.2", + "version": "1.12.4", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library" diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 9a13bd2149..4b76fe42e4 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-techdocs-react +## 1.2.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + ## 1.2.4-next.1 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index ebba33cdca..283e3afcff 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.2.4-next.1", + "version": "1.2.4", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 5011b3471c..7e8f3056e5 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-techdocs +## 1.10.5 + +### Patch Changes + +- d2cc139: Update path in Readme for Plugin Techdocs to show the correct setup information. +- 5863cf7: The `techdocs.builder` config is now optional and it will default to `local`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-react@0.1.2 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/integration@1.11.0 + - @backstage/frontend-plugin-api@0.6.5 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-search-react@1.7.11 + - @backstage/plugin-techdocs-react@1.2.4 + ## 1.10.5-next.2 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index d75774cb10..bc9c4c8022 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.10.5-next.2", + "version": "1.10.5", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin" diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 57e3663d5f..f689d8090f 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-user-settings-backend +## 0.2.17 + +### Patch Changes + +- d229dc4: Move path utilities from `backend-common` to the `backend-plugin-api` package. +- Updated dependencies + - @backstage/backend-common@0.22.0 + - @backstage/backend-plugin-api@0.6.18 + - @backstage/plugin-auth-node@0.4.13 + ## 0.2.17-next.1 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index f8b46d8d3f..ac5e5d9af2 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.2.17-next.1", + "version": "0.2.17", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin" diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 01d7c1214c..85930ec041 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-user-settings +## 0.8.6 + +### Patch Changes + +- 131e5cb: Fix broken links in README. +- Updated dependencies + - @backstage/core-compat-api@0.2.5 + - @backstage/core-components@0.14.7 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/core-app-api@1.12.5 + - @backstage/frontend-plugin-api@0.6.5 + ## 0.8.6-next.2 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 1af0b2fb41..b3f0f244c8 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.6-next.2", + "version": "0.8.6", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin" diff --git a/yarn.lock b/yarn.lock index 52606f44db..f10de89d74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3574,18 +3574,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-client@npm:^1.6.4": - version: 1.6.4 - resolution: "@backstage/catalog-client@npm:1.6.4" - dependencies: - "@backstage/catalog-model": ^1.4.5 - "@backstage/errors": ^1.2.4 - cross-fetch: ^4.0.0 - uri-template: ^2.0.0 - checksum: af3537d04f0abd6e6f3e49c7623994cc83db6efb2776fff5d59faee26c598840486e42a99c2bbb4a1b6ff97ad97ac857e913830cad62cda7cd71eef74cf2e179 - languageName: node - linkType: hard - "@backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": version: 0.0.0-use.local resolution: "@backstage/catalog-client@workspace:packages/catalog-client" @@ -3599,19 +3587,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@npm:^1.4.3, @backstage/catalog-model@npm:^1.4.5": - version: 1.4.5 - resolution: "@backstage/catalog-model@npm:1.4.5" - dependencies: - "@backstage/errors": ^1.2.4 - "@backstage/types": ^1.1.1 - ajv: ^8.10.0 - lodash: ^4.17.21 - checksum: 34aaa4b82d29bf3b0a4b52552f8eb8189041df826f87a7bbdef5107a1efb8ce1f7eb1c1a343868718ca2af9be76d9f5184f6a92076d893d3a3951d16881647b7 - languageName: node - linkType: hard - -"@backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@^1.4.3, @backstage/catalog-model@^1.4.5, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3863,7 +3839,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@^1.1.1, @backstage/config@^1.2.0, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@^1.1.1, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -3935,109 +3911,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.13.10": - version: 0.13.10 - resolution: "@backstage/core-components@npm:0.13.10" - dependencies: - "@backstage/config": ^1.1.1 - "@backstage/core-plugin-api": ^1.8.2 - "@backstage/errors": ^1.2.3 - "@backstage/theme": ^0.5.0 - "@backstage/version-bridge": ^1.0.7 - "@date-io/core": ^1.3.13 - "@material-table/core": ^3.1.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^23.0.0 - "@types/react": ^16.13.1 || ^17.0.0 - "@types/react-sparklines": ^1.7.0 - "@types/react-text-truncate": ^0.14.0 - ansi-regex: ^6.0.1 - classnames: ^2.2.6 - d3-selection: ^3.0.0 - d3-shape: ^3.0.0 - d3-zoom: ^3.0.0 - dagre: ^0.8.5 - linkify-react: 4.1.3 - linkifyjs: 4.1.3 - lodash: ^4.17.21 - pluralize: ^8.0.0 - qs: ^6.9.4 - rc-progress: 3.5.1 - react-helmet: 6.1.0 - react-hook-form: ^7.12.2 - react-idle-timer: 5.6.2 - react-markdown: ^8.0.0 - react-sparklines: ^1.7.0 - react-syntax-highlighter: ^15.4.5 - react-text-truncate: ^0.19.0 - react-use: ^17.3.2 - react-virtualized-auto-sizer: ^1.0.11 - react-window: ^1.8.6 - remark-gfm: ^3.0.1 - zen-observable: ^0.10.0 - zod: ^3.22.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 - languageName: node - linkType: hard - -"@backstage/core-components@npm:^0.14.4": - version: 0.14.4 - resolution: "@backstage/core-components@npm:0.14.4" - dependencies: - "@backstage/config": ^1.2.0 - "@backstage/core-plugin-api": ^1.9.2 - "@backstage/errors": ^1.2.4 - "@backstage/theme": ^0.5.3 - "@backstage/version-bridge": ^1.0.8 - "@date-io/core": ^1.3.13 - "@material-table/core": ^3.1.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^24.0.0 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - "@types/react-sparklines": ^1.7.0 - "@types/react-text-truncate": ^0.14.0 - ansi-regex: ^6.0.1 - classnames: ^2.2.6 - d3-selection: ^3.0.0 - d3-shape: ^3.0.0 - d3-zoom: ^3.0.0 - dagre: ^0.8.5 - linkify-react: 4.1.3 - linkifyjs: 4.1.3 - lodash: ^4.17.21 - pluralize: ^8.0.0 - qs: ^6.9.4 - rc-progress: 3.5.1 - react-helmet: 6.1.0 - react-hook-form: ^7.12.2 - react-idle-timer: 5.7.2 - react-markdown: ^8.0.0 - react-sparklines: ^1.7.0 - react-syntax-highlighter: ^15.4.5 - react-text-truncate: ^0.19.0 - react-use: ^17.3.2 - react-virtualized-auto-sizer: ^1.0.11 - react-window: ^1.8.6 - remark-gfm: ^3.0.1 - zen-observable: ^0.10.0 - zod: ^3.22.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: b96721b267daeec5a73a8b487f96ab5b5b3a85b8b113b2e45c5b733f3201af0d5cda3d7a325f5f68a4057ccbe78beac9f239b51ceb3099d3b0001709ec16928b - languageName: node - linkType: hard - -"@backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@^0.14.4, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -4108,6 +3982,57 @@ __metadata: languageName: unknown linkType: soft +"@backstage/core-components@npm:^0.13.10": + version: 0.13.10 + resolution: "@backstage/core-components@npm:0.13.10" + dependencies: + "@backstage/config": ^1.1.1 + "@backstage/core-plugin-api": ^1.8.2 + "@backstage/errors": ^1.2.3 + "@backstage/theme": ^0.5.0 + "@backstage/version-bridge": ^1.0.7 + "@date-io/core": ^1.3.13 + "@material-table/core": ^3.1.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^23.0.0 + "@types/react": ^16.13.1 || ^17.0.0 + "@types/react-sparklines": ^1.7.0 + "@types/react-text-truncate": ^0.14.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + linkify-react: 4.1.3 + linkifyjs: 4.1.3 + lodash: ^4.17.21 + pluralize: ^8.0.0 + qs: ^6.9.4 + rc-progress: 3.5.1 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-idle-timer: 5.6.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-text-truncate: ^0.19.0 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.11 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.10.0 + zod: ^3.22.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 + languageName: node + linkType: hard + "@backstage/core-plugin-api@^1.8.2, @backstage/core-plugin-api@^1.9.2, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" @@ -4255,26 +4180,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/frontend-plugin-api@npm:^0.6.4": - version: 0.6.4 - resolution: "@backstage/frontend-plugin-api@npm:0.6.4" - dependencies: - "@backstage/core-components": ^0.14.4 - "@backstage/core-plugin-api": ^1.9.2 - "@backstage/types": ^1.1.1 - "@backstage/version-bridge": ^1.0.8 - "@material-ui/core": ^4.12.4 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - lodash: ^4.17.21 - zod: ^3.22.4 - zod-to-json-schema: ^3.21.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 2ae2919147dcfd8a5b4379059ecb76461a27f38159c398915384db7a7396bc65451cf696d955e4ba23eb6dfb549395624d09412d0c9c776072d3f003e5f1cba8 - languageName: node - linkType: hard - "@backstage/frontend-plugin-api@workspace:^, @backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api": version: 0.0.0-use.local resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" @@ -4338,24 +4243,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@npm:^1.1.26": - version: 1.1.26 - resolution: "@backstage/integration-react@npm:1.1.26" - dependencies: - "@backstage/config": ^1.2.0 - "@backstage/core-plugin-api": ^1.9.2 - "@backstage/integration": ^1.10.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@types/react": ^16.13.1 || ^17.0.0 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 590e8293a0e21a034126c1a00c1c69c66bba81dcbf39675336c092b250ee139effe874e443a99521751b3c1aa9b103603bc8a3177a9f115ff0f1a0249ac5eed6 - languageName: node - linkType: hard - "@backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" @@ -4380,23 +4267,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration@npm:^1.10.0": - version: 1.10.0 - resolution: "@backstage/integration@npm:1.10.0" - dependencies: - "@azure/identity": ^4.0.0 - "@backstage/config": ^1.2.0 - "@backstage/errors": ^1.2.4 - "@octokit/auth-app": ^4.0.0 - "@octokit/rest": ^19.0.3 - cross-fetch: ^4.0.0 - git-url-parse: ^14.0.0 - lodash: ^4.17.21 - luxon: ^3.0.0 - checksum: 86324df95b30ff6ae92fcc605bd21d0f12cdc0553d555ebe8977a1be6554819ad8723eabcd99d1574c7c244b4822a6628d01273557040c89360394ba3198f6b9 - languageName: node - linkType: hard - "@backstage/integration@workspace:^, @backstage/integration@workspace:packages/integration": version: 0.0.0-use.local resolution: "@backstage/integration@workspace:packages/integration" @@ -5449,18 +5319,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@npm:^1.0.20, @backstage/plugin-catalog-common@npm:^1.0.22": - version: 1.0.22 - resolution: "@backstage/plugin-catalog-common@npm:1.0.22" - dependencies: - "@backstage/catalog-model": ^1.4.5 - "@backstage/plugin-permission-common": ^0.7.13 - "@backstage/plugin-search-common": ^1.2.11 - checksum: f468ade184d5e535cc27cbb27a9dbd6cd21c1601b5a84167d2ea1004f471180ef8bf148df5561b5557c332bdc01480d020e93f62915f029cc728802cebf8e255 - languageName: node - linkType: hard - -"@backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@^1.0.20, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -5569,43 +5428,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.11.3, @backstage/plugin-catalog-react@npm:^1.9.3": - version: 1.11.3 - resolution: "@backstage/plugin-catalog-react@npm:1.11.3" - dependencies: - "@backstage/catalog-client": ^1.6.4 - "@backstage/catalog-model": ^1.4.5 - "@backstage/core-components": ^0.14.4 - "@backstage/core-plugin-api": ^1.9.2 - "@backstage/errors": ^1.2.4 - "@backstage/frontend-plugin-api": ^0.6.4 - "@backstage/integration-react": ^1.1.26 - "@backstage/plugin-catalog-common": ^1.0.22 - "@backstage/plugin-permission-common": ^0.7.13 - "@backstage/plugin-permission-react": ^0.4.22 - "@backstage/types": ^1.1.1 - "@backstage/version-bridge": ^1.0.8 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^24.0.0 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - classnames: ^2.2.6 - lodash: ^4.17.21 - material-ui-popup-state: ^1.9.3 - qs: ^6.9.4 - react-use: ^17.2.4 - yaml: ^2.0.0 - zen-observable: ^0.10.0 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: d04919bff692094bb145d8479aae47368911f3adc92644b4742e5db1296af9a2673cf4341995b9c65e660cd608d3b59a136b9c164ab2df51bdc3ccbaf5af71fd - languageName: node - linkType: hard - -"@backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@^1.11.3, @backstage/plugin-catalog-react@^1.9.3, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -6444,7 +6267,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-common@^0.7.13, @backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": +"@backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" dependencies: @@ -6482,7 +6305,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-react@^0.4.22, @backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": +"@backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" dependencies: @@ -7216,7 +7039,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-search-common@^1.2.11, @backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": +"@backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": version: 0.0.0-use.local resolution: "@backstage/plugin-search-common@workspace:plugins/search-common" dependencies: @@ -7753,23 +7576,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@npm:^0.5.0, @backstage/theme@npm:^0.5.3": - version: 0.5.3 - resolution: "@backstage/theme@npm:0.5.3" - dependencies: - "@emotion/react": ^11.10.5 - "@emotion/styled": ^11.10.5 - "@mui/material": ^5.12.2 - peerDependencies: - "@material-ui/core": ^4.12.2 - "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - checksum: ac6c3bbd73294385f73aa91e04f8bf3a1bb78cadc0e43034760ebf19e86814ed2d679f2641bb086aa9e305a24d923d8c3480b2de3bff55189c19bc5dfdce1814 - languageName: node - linkType: hard - -"@backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@^0.5.0, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -7800,7 +7607,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@^1.0.8, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": +"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": version: 0.0.0-use.local resolution: "@backstage/version-bridge@workspace:packages/version-bridge" dependencies: From c65938d64d769cb3e8f27f495756544897a5ea31 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 May 2024 16:58:20 +0200 Subject: [PATCH 398/567] docs/overview: update roadmap for fall 2024 Signed-off-by: Patrik Oldsberg --- docs/overview/roadmap.md | 120 +++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 186619d1df..f53f79f431 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -6,91 +6,87 @@ description: Roadmap of Backstage ## The Backstage Roadmap -Backstage is currently under rapid development. This page details the project's -public roadmap, the result of ongoing collaboration between the core maintainers -and the broader Backstage community. +Backstage is still under rapid development, and this page details the project's +public roadmap. This not a complete list of all work happening in and around the +project, it only highlights the highest priority initiatives worked on by the +core maintainers. -The Backstage roadmap lays out both [“what's next”](#whats-next) and ["future work"](#future-work). With "next" we mean features planned for release within -the ongoing quarter from July through September 2022. With "future" we mean -features on the radar, but not yet scheduled. +## 2024 Fall Roadmap -| [What's next](#whats-next) | [Future work](#future-work) | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| [Backend Services (MVP)](#backend-services-mvp)
    [Backstage Security Audit](#backstage-security-audit)
    [Backstage Threat Model](#backstage-threat-model)
    [Software Catalog pagination](#software-catalog-pagination)
    [More SIGs](#more-sigs) | Ease of onboarding
    Composable Homepage 1.0
    Creator experience
    GraphQL
    Telemetry | +The initiatives listed below are planned for release within the next half-year, starting in May 2024. The roadmap is updated every 6 months, and the next update is planned for November 2024. -The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. -Third-party contributions are also not currently included in the roadmap. Let us -know about any ongoing developments and we're happy to include them here as -well. +### Backend System 1.0 -## What's next +The goal of this initiative is the stable 1.0 release of the [new backend system](../backend-system/index.md). +This includes ensuring that all documentation is up to date, and includes API +reviews and refactoring efforts to ensure that what is released is both stable +and evolvable. You can follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/24493). -The feature set below is planned for the ongoing quarter, and grouped by theme. -The list order doesn't necessarily reflect priority, and the development/release -cycle will vary based on maintainer schedules. +As part of this initiative, there will also be and exploration on how to +simplify extension of backend services. It is not currently possible to augment +backend services through declarative integration, they are instead only +customizable through complete replacement. This also limits the ability to +modularize services and scale ownership of the implementations. The goal is to +provide a more flexible and scalable way to extend backend services. -### Backend Services (MVP) +### New Frontend System - Ready for Adoption -To better scale and maintain the Backstage instances, a backend services system -is planned to be introduced as part of the software architecture. This layer of -backend services will help in decoupling the various modules (e.g. Catalog and -Scaffolder) from the frontend experience. +The [new fronted system](../frontend-system/index.md) still needs more work, and +the next milestone is to improve it to the point where there is enough +confidence in the design to start encouraging adoption in the community. You can +follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/19545). +This milestone also includes reaching and executing [rollout phase 2](https://github.com/backstage/backstage/issues/19545#issuecomment-1766069146). -After the experimentation and design happened in the past quarter, soon we plan to release a first version to start providing the first benefits to adopters and developers. +Once the initial milestone is reached, the goal is to also build out broader +support for the new frontend system in the core plugins. ### Backstage Security Audit -This is the continuation of the initiative started in the previous quarters. This -quarter will see the publication of the report describing the outcome of the -audit, together the first fixes and the development of some of the changes -required to address the vulnerabilities. +This is the second security audit of the Backstage project. It is done together, +and with the support of the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/). +This time the audit will in particular focus on the recently introduced +[authentication system](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution), +but also cover other parts of the project. -This initiative is the first of a broader Security Strategy for Backstage. The -purpose of the Security Audit is to involve third-party companies in auditing -the platform. The benefit for the adopters is clear: we want Backstage to be as -secure as possible, and we want to make it reliable through a specific -initiative. +### Plugin Metadata -This initiative is done together with, and with the support of, the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/). +The goal of this initiative is to provide better machine readable metadata for +Backstage packages, available both at runtime, at build-time and as part of +package registries. We want to surface information such as what packages make up +a particular plugin, what features it provides, and more generally laying the +foundation for an evolvable plugin metadata system. -### Backstage Threat Model +### MUI v5 Green-light -This is another (relevant) initiative planned to make Backstage a secure product for the adopters. The goals of this initiative are: +Material-UI v4 is still the officially supported version of MUI in Backstage. +While we have heard that adopters have had success using MUI 5, this is still an +untested path with known bugs. The goal of this initiative is to iron out any +remaining issues of gaps, and then provide a green light for migration to MUI 5. -1. Understand where security investment and attention is needed. -2. Guide the upcoming security audit. -3. Communicate expectations to Backstage adopters and inform and attract security researchers. +### Configuration Improvements -The planned artifacts are: +This initiative aims to improve the configuration experience and reliability in +Backstage. Areas for improvement include the way that configuration schema is +loaded, the way that plugins access configuration that is not owned by them, how +plugins read configuration, and how configuration visibility is handled. -- Concise high level threat model that will be included as part of the Backstage security documentation. -- Granular threat model created in conjunction with the security audit to inform further security investment areas for Backstage. +### Versioned Documentation -### Software Catalog pagination +The goal of this initiative is to provide versioned documentation at +[backstage.io](https://backstage.io). This lets us provide documentation that is +both up-to-date while at the same time not ahead of the latest release. -Today adopters with a big catalog (with several thousands of software components) might not have an ideal end-user experience when viewing the `/catalog` page. The issue is related to how the entities are fetched by the frontend. In order to provide a better end-user experience the pagination of the catalog’s entities needs to be enforced. Some experimentation is already completed but in this quarter we plan to continue, and hopefully complete, this relevant enhancement. +### Rework Pull Request & Issue Process -### More SIGs +Our current review and issue triage process is centered around either core- or +project area maintainers. The goal of this initiative is to make it simpler for +more members of the community to be involved and contribute to this process. -In the last quarter we launched the [Catalog SIG (Special Interest Group)](https://github.com/backstage/community/tree/main/sigs/sig-catalog) to better coordinate the increasing number of contributions to the project. We think that this is the proper path to follow to engage more with the contributors. For this reason we will launch other SIGs dedicated to the most interesting topics for the community. +### Catalog Observability -## Future work - -The following feature list doesn't represent a commitment to develop, and the -list order doesn't reflect any priority or importance, but these features are on -the maintainers' radar, with clear interest expressed by the community. - -- **Ease of onboarding:** A faster (with less development) and easier setup of - Backstage and the most relevant/adopted plugins. -- **Composable Homepage 1.0:** Driving this to 1.0 by adding some composable - components. -- **Creator experience:** Provide a better Backstage user experience through - visual guidelines and templates, especially navigation across plug-ins and - portal functionalities. -- **[GraphQL](https://graphql.org/) support:** Introduce the ability to query - Backstage backend services with a standard query language for APIs. -- **Telemetry:** To efficiently generate logging and metrics in such a way that - adopters can get insights so that Backstage can be monitored and improved. +The goal of this initiative is to provide better tools for debugging catalog +ingestion issues and to more generally reduce friction for setting up and +maintaining the software catalog. ## How to influence the roadmap From f145a0458b8030c98e4b896c1d6a274eaec1287d Mon Sep 17 00:00:00 2001 From: Tavi Nolan Date: Wed, 17 Apr 2024 15:48:51 +0100 Subject: [PATCH 399/567] Fixed test imports, added changeset Signed-off-by: Tavi Nolan --- .changeset/cyan-paws-beg.md | 5 +++++ .../src/actions/github.test.ts | 1 - .../src/actions/githubPullRequest.test.ts | 19 +++++++++---------- .../src/actions/githubPullRequest.ts | 2 +- .../src/actions/githubRepoCreate.test.ts | 1 - .../src/actions/githubWebhook.test.ts | 7 +++---- 6 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 .changeset/cyan-paws-beg.md diff --git a/.changeset/cyan-paws-beg.md b/.changeset/cyan-paws-beg.md new file mode 100644 index 0000000000..87c2b746a4 --- /dev/null +++ b/.changeset/cyan-paws-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Added handling for dry run to githubPullRequest and githubWebhook and added tests for this functionality diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index 58c43616e4..9eda9a2eb0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -41,7 +41,6 @@ import { GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { when } from 'jest-when'; import { createPublishGithubAction } from './github'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index 150ed39e50..613ebc7c59 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -14,21 +14,20 @@ * limitations under the License. */ -import { - ActionContext, - TemplateAction, -} from '@backstage/plugin-scaffolder-node'; +import { createRootLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; import { GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; - -import { ConfigReader } from '@backstage/config'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { createMockDirectory } from '@backstage/backend-test-utils'; -import { createPublishGithubPullRequestAction } from './githubPullRequest'; -import { createRootLogger } from '@backstage/backend-common'; +import { + ActionContext, + TemplateAction, +} from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; +import { createPublishGithubPullRequestAction } from './githubPullRequest'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; // Make sure root logger is initialized ahead of FS mock createRootLogger(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index de70f56399..ce199d5f56 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -26,7 +26,7 @@ import { serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; import { Octokit } from 'octokit'; -import { CustomErrorBase, InputError } from '@backstage/errors'; +import { InputError, CustomErrorBase } from '@backstage/errors'; import { resolveSafeChildPath } from '@backstage/backend-common'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { getOctokitOptions } from './helpers'; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index df2dd29eb8..1750acdb9a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -30,7 +30,6 @@ import { GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { when } from 'jest-when'; import { createGithubRepoCreateAction } from './githubRepoCreate'; import { entityRefToName } from './gitHelpers'; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts index 6c0335c509..30bdcef20d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts @@ -14,16 +14,15 @@ * limitations under the License. */ +import { createGithubWebhookAction } from './githubWebhook'; import { + ScmIntegrations, DefaultGithubCredentialsProvider, GithubCredentialsProvider, - ScmIntegrations, } from '@backstage/integration'; - +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/config'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { createGithubWebhookAction } from './githubWebhook'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; const mockOctokit = { rest: { From b7de62385ca04eab5437040af6d9549ea54a1a7c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 May 2024 17:17:34 +0200 Subject: [PATCH 400/567] backend-app-api: fix object meta null proto crash Signed-off-by: Patrik Oldsberg --- .changeset/brave-apples-move.md | 5 +++++ .../backend-app-api/src/logging/WinstonLogger.test.ts | 8 ++++++++ packages/backend-app-api/src/logging/WinstonLogger.ts | 8 ++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .changeset/brave-apples-move.md diff --git a/.changeset/brave-apples-move.md b/.changeset/brave-apples-move.md new file mode 100644 index 0000000000..7a1fa86a56 --- /dev/null +++ b/.changeset/brave-apples-move.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Fixed a potential crash when passing an object with a `null` prototype as log meta. diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index d025719d9a..06f9cdd4c3 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -65,6 +65,10 @@ describe('WinstonLogger', () => { level: 'error', message: { nested: 'hello (world) from nested object', + null: null, + nullProto: Object.create(null, { + foo: { value: 'hello foo', enumerable: true }, + }), }, }; @@ -74,6 +78,10 @@ describe('WinstonLogger', () => { ...log, message: { nested: '[REDACTED] (world) from nested object', + null: null, + nullProto: { + foo: 'hello foo', // read only prop is not redacted + }, }, }), ); diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 8d4b37bb8d..3fcb4142b6 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -87,11 +87,15 @@ export class WinstonLogger implements RootLoggerService { const replace = (obj: TransformableInfo) => { for (const key in obj) { - if (obj.hasOwnProperty(key)) { + if (Object.hasOwn(obj, key)) { if (typeof obj[key] === 'object') { obj[key] = replace(obj[key] as TransformableInfo); } else if (typeof obj[key] === 'string') { - obj[key] = obj[key]?.replace(redactionPattern, '[REDACTED]'); + try { + obj[key] = obj[key]?.replace(redactionPattern, '[REDACTED]'); + } catch { + /* ignore read only properties */ + } } } } From bab063c8d1143ce09a7f9465034f58d134a43c23 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 14 May 2024 17:26:01 +0200 Subject: [PATCH 401/567] Apply suggestions from code review Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- docs/overview/roadmap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index f53f79f431..1a41eedd63 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -22,7 +22,7 @@ This includes ensuring that all documentation is up to date, and includes API reviews and refactoring efforts to ensure that what is released is both stable and evolvable. You can follow along with this work in the [meta issue](https://github.com/backstage/backstage/issues/24493). -As part of this initiative, there will also be and exploration on how to +As part of this initiative, there will also be an exploration on how to simplify extension of backend services. It is not currently possible to augment backend services through declarative integration, they are instead only customizable through complete replacement. This also limits the ability to @@ -61,7 +61,7 @@ foundation for an evolvable plugin metadata system. Material-UI v4 is still the officially supported version of MUI in Backstage. While we have heard that adopters have had success using MUI 5, this is still an untested path with known bugs. The goal of this initiative is to iron out any -remaining issues of gaps, and then provide a green light for migration to MUI 5. +remaining issues or gaps, and then provide a green light for migration to MUI 5. ### Configuration Improvements From 14495e165e3f3667b6b04ec071c87ffc2ba304fd Mon Sep 17 00:00:00 2001 From: Kyle Smith Date: Tue, 14 May 2024 11:58:15 -0500 Subject: [PATCH 402/567] docs: change Twilio's ADOPTERS.md contact to @alecjacobs5401 My last day at Twilio is 2024-05-17 and Alec Jacobs will take over as Tech Lead and point person. Update Twilio's contact in ADOPTERS.md to reflect the change. Follows #11943 Signed-off-by: Kyle Smith --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 4a6f726fcd..2bb76a199e 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -154,7 +154,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Lendingkart](https://www.lendingkart.com/) | [Dinesh Rajpoot](mailto:dinesh.rajpoot@lendingkart.com) | Service catalog, Software templates to enforce best practices and tech insights to track mandates & migrations. | | [Meltwater](https://underthehood.meltwater.com) | [@spier](https://github.com/spier), [@remen](https://github.com/remen) | Improving developer experience by centralizing documentation and internal APIs. Goal: Foster InnerSource collaboration and speed up onboarding time in our 500+ people Product & Engineering org. | | [Doctolib](https://doctolib.engineering/) | [@djiit](https://github.com/djiit) | Rails modularization effort awareness, tech organization discoverability. Improving the daily workflows and collaboration processes of our engineers. | -| [Twilio](https://www.twilio.com) | [Kyle Smith](https://github.com/knksmith57) | Developer portal, universal software catalog, and centralized taxonomy platform. | +| [Twilio](https://www.twilio.com) | [Alec Jacobs](https://github.com/alecjacobs5401) | Developer portal, universal software catalog, and centralized taxonomy platform. | | [OVHcloud](https://www.ovhcloud.com/fr/) | [Jean-Philippe Blary](https://github.com/blaryjp), [Arnaud Bauer](mailto:arnaud.bauer@ovhcloud.com), [Flavien Chantelot](https://github.com/Dorn-) | We're providing Backstage to our collaborators to ease their daily jobs, and let them extends it using plugins. | | [Procter & Gamble](https://us.pg.com/) | [Binita Nayak](https://github.com/binitan), | P&G leverages Backstage to build internal developer portal to ensure developers' happiness. This developer portal shall act as single source of information needed by development teams to seamlessly create, find and maintain their software components/resources/documentation. | | [SANS Institute](https://www.sans.org) | [Christopher Klewin](mailto:cklewin@sans.org) | Developer portal for centralized visibility, reporting, and tooling across multiple organizations. | From 881e49d94c2bf54bb7007c61a7c4c75400135c49 Mon Sep 17 00:00:00 2001 From: Erik Sjoholm Date: Tue, 14 May 2024 10:33:50 -0700 Subject: [PATCH 403/567] Remove changes to scaffolder-backend api-report generated by running yarn build:api-reports Signed-off-by: Erik Sjoholm --- plugins/scaffolder-backend/api-report.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 8a9623d78f..c141733ec4 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -302,10 +302,7 @@ export const createPublishGitlabMergeRequestAction: (options: { }) => TemplateAction_2< { repoUrl: string; - title: string - /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead - */; + title: string; description: string; branchName: string; targetBranchName?: string | undefined; From aeb8a06dc28aec29f4e827ef30a76abbc528e178 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Wed, 15 May 2024 08:32:43 +0530 Subject: [PATCH 404/567] Updated the backend-system/building-backends documents Signed-off-by: Aditya Kumar --- .../building-backends/01-index.md | 8 +++- .../building-backends/08-migrating.md | 42 ++++++++++++++----- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/docs/backend-system/building-backends/01-index.md b/docs/backend-system/building-backends/01-index.md index 4caa6fd524..44aa0c7e28 100644 --- a/docs/backend-system/building-backends/01-index.md +++ b/docs/backend-system/building-backends/01-index.md @@ -6,8 +6,12 @@ sidebar_label: Overview description: Building backends using the new backend system --- -> NOTE: If you have an existing backend that is not yet using the new backend -> system, see [migrating](./08-migrating.md). +:::note Note + +If you have an existing backend that is not yet using the new backend +system, see [migrating](./08-migrating.md). + +::: This section covers how to set up and customize your own Backstage backend. It covers some aspects of how backend instances fit into the larger system, but for a more in-depth explanation of the role of backends in the backend system, see [the architecture section](../architecture/02-backends.md). diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index fc0bf35267..ae41193b11 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -201,10 +201,14 @@ const legacyPlugin = makeLegacyPlugin( After this, your backend will know how to instantiate your thing on demand and place it in the legacy plugin environment. -> NOTE: If you happen to be dealing with a service ref that does NOT have a -> default implementation, but rather has a separate service factory, then you -> will also need to import that factory and pass it to the `services` array -> argument of `createBackend`. +:::note Note + +If you happen to be dealing with a service ref that does NOT have a +default implementation, but rather has a separate service factory, then you +will also need to import that factory and pass it to the `services` array +argument of `createBackend`. + +::: ## Cleaning Up the Plugins Folder @@ -216,10 +220,14 @@ maintained by the Backstage maintainers, you may find that they have already been migrated to the new backend system. This section describes some specific such migrations you can make. -> NOTE: For each of these, note that your backend still needs to have a -> dependency (e.g. in `packages/backend/package.json`) to those plugin packages, -> and they still need to be configured properly in your app-config. Those -> mechanisms still work just the same as they used to in the old backend system. +:::note Note + +For each of these, note that your backend still needs to have a +dependency (e.g. in `packages/backend/package.json`) to those plugin packages, +and they still need to be configured properly in your app-config. Those +mechanisms still work just the same as they used to in the old backend system. + +::: ### The App Plugin @@ -880,7 +888,11 @@ auth: - resolver: emailMatchingUserEntityAnnotation ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: #### Auth Plugin Modules and Their Resolvers @@ -1137,7 +1149,11 @@ backend.add(import('@backstage/plugin-search-backend/alpha')); /* highlight-add-end */ ``` -> Note: this will use the Lunr search engine which stores its index in memory +:::note Note + +This will use the Lunr search engine which stores its index in memory. + +::: #### Search Engines @@ -1226,7 +1242,11 @@ backend.add( /* highlight-add-end */ ``` -> Note: The above example includes a default allow-all policy. If that is not what you want, do not add the second line and instead investigate one of the options below. +:::note Note + +The above example includes a default allow-all policy. If that is not what you want, do not add the second line and instead investigate one of the options below. + +::: #### Custom Permission Policy From 7f0268486a335a1fb4455e1c08258b4a88e2569d Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Wed, 15 May 2024 08:47:57 +0300 Subject: [PATCH 405/567] fix: do not show scrollbars in notification description if not needed Signed-off-by: Heikki Hellgren --- .changeset/four-adults-mix.md | 5 +++++ .../src/components/NotificationsTable/NotificationsTable.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/four-adults-mix.md diff --git a/.changeset/four-adults-mix.md b/.changeset/four-adults-mix.md new file mode 100644 index 0000000000..5013166dc6 --- /dev/null +++ b/.changeset/four-adults-mix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Do not always show scrollbars in notification description diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index db2b351432..72a27975f6 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -43,7 +43,7 @@ const ThrottleDelayMs = 1000; const useStyles = makeStyles({ description: { maxHeight: '5rem', - overflow: 'scroll', + overflow: 'auto', }, severityItem: { alignContent: 'center', From 3568039ef23dab1d482e5be6cb07342aff912b08 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 May 2024 10:16:02 +0200 Subject: [PATCH 406/567] catalog-backend-module-gitlab: fix release Signed-off-by: Patrik Oldsberg --- .../CHANGELOG.md | 2 +- .../package.json | 94 +++++++++---------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 5500adc6b1..ee1ceb70b7 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,6 +1,6 @@ # @backstage/plugin-catalog-backend-module-gitlab -## 0.3.15 +## 0.3.16 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 83e2a47be9..109dee369e 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,8 +1,53 @@ { + "name": "@backstage/plugin-catalog-backend-module-gitlab", + "version": "0.3.16", + "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module" }, - "configSchema": "config.d.ts", + "publishConfig": { + "access": "public" + }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-gitlab" + }, + "license": "Apache-2.0", + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "main": "src/index.ts", + "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, + "files": [ + "config.d.ts", + "dist" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" + }, "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", @@ -18,7 +63,6 @@ "node-fetch": "^2.6.7", "uuid": "^9.0.0" }, - "description": "A Backstage catalog backend module that helps integrate towards GitLab", "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", @@ -28,49 +72,5 @@ "luxon": "^3.0.0", "msw": "^1.0.0" }, - "exports": { - ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", - "./package.json": "./package.json" - }, - "files": [ - "config.d.ts", - "dist" - ], - "homepage": "https://backstage.io", - "keywords": [ - "backstage" - ], - "license": "Apache-2.0", - "main": "src/index.ts", - "name": "@backstage/plugin-catalog-backend-module-gitlab", - "publishConfig": { - "access": "public" - }, - "repository": { - "directory": "plugins/catalog-backend-module-gitlab", - "type": "git", - "url": "https://github.com/backstage/backstage" - }, - "scripts": { - "build": "backstage-cli package build", - "clean": "backstage-cli package clean", - "lint": "backstage-cli package lint", - "postpack": "backstage-cli package postpack", - "prepack": "backstage-cli package prepack", - "start": "backstage-cli package start", - "test": "backstage-cli package test" - }, - "types": "src/index.ts", - "typesVersions": { - "*": { - "alpha": [ - "src/alpha.ts" - ], - "package.json": [ - "package.json" - ] - } - }, - "version": "0.3.15" + "configSchema": "config.d.ts" } From 8985c63966080b3fdb9055d6bfdc055240866dac Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 14 May 2024 13:25:28 +0200 Subject: [PATCH 407/567] docs: 1.27 release notes Signed-off-by: Vincenzo Scamporlino --- docs/releases/v1.27.0.md | 83 ++++++++++++++++++++++++++++++++++ microsite/docusaurus.config.ts | 2 +- microsite/sidebars.json | 1 + 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 docs/releases/v1.27.0.md diff --git a/docs/releases/v1.27.0.md b/docs/releases/v1.27.0.md new file mode 100644 index 0000000000..123d9663ad --- /dev/null +++ b/docs/releases/v1.27.0.md @@ -0,0 +1,83 @@ +--- +id: v1.27.0 +title: v1.27.0 +description: Backstage Release v1.27.0 +--- + +These are the release notes for the v1.27.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### Backend System Deprecations + +As part of the [work towards a stable 1.0 release of the new backend system](https://github.com/backstage/backstage/issues/24493), we will deprecate and move or remove several utilities from `@backstage/backend-common`. This release contains a few of these deprecations, with more to be expected in the future. The long-term goal is to completely deprecate and remove the `@backstage/backend-common` package. + +### Hierarchical Domains + +The Backstage System Model has been tweaked with the addition of a hierarchy of `Domain` entities. The change includes a new `spec.subdomainOf` property on the `Domain` entity, which can be used to express that a domain has a `partOf` (and conversely, `hasPart`) relation toward another domain. + +Contributed by [@dawngerpony](https://github.com/dawngerpony) and [@janogonzalez](https://github.com/janogonzalez) in [#17125](https://github.com/backstage/backstage/pull/17125). + +### Scaffolder workspace serialization + +Added experimental support for serialization of workspaces in the scaffolder. By serializing the workspace, it is possible to re-run the task in a non-sticky way. This means that the task can be restored and retried on a different scaffolder task worker. + +To enable this feature, set the `EXPERIMENTAL_workspaceSerialization` option to `true` in the `scaffolder` section of the `app-config.yaml` file: + +```yaml +scaffolder: + EXPERIMENTAL_workspaceSerialization: true +``` + +Contributed by [@acierto](https://github.com/acierto) in [#24570](https://github.com/backstage/backstage/pull/24570). + +### Scaffolder `notification:send` action + +The new `notification:send` action allows sending notifications from templates. This can be used to send notifications to users when executing a template. Please note that the notifications system is still under development. + +To install this action, add the new module to your backend: + +```diff ++ backend.add(import('@backstage/plugin-scaffolder-backend-module-notifications')); +``` + +Contributed by [@drodil](https://github.com/drodil) in [#24588](https://github.com/backstage/backstage/pull/24588). + +### Backend Authentication + +The requirement to configure a secret for backend authentication in production has been removed. It is now only needed if you rely on the [legacy authentication mechanism](https://backstage.io/docs/auth/service-to-service-auth#external-callers-legacy). If you don’t configure any secrets you will also not be able to generate tokens with the `TokenManager` service, although use of this service is discouraged as it has been replaced by the `AuthService`. + +### User Authentication + +The `auth` backend plugin now provides an `authOwnershipResolutionExtensionPoint` that lets you override the default ownership resolution used by sign-in resolvers. This allows you to customize this logic for all sign-in resolvers without replacing them. + +Contributed by [@drodil](https://github.com/drodil) in [#22765](https://github.com/backstage/backstage/pull/22765). + +### Events support for GitLab Entity and Org Discovery + +The `GitlabDiscoveryEntityProvider` and `GitlabOrgDiscoveryEntityProvider` can now be configured to receive events from GitLab. This allows for the automatic discovery of entities in Backstage when groups or users are created or updated in GitLab. + +Contributed by [@elaine-mattos](https://github.com/elaine-mattos) in [#23373](https://github.com/backstage/backstage/pull/23373). + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.27.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index 5f6dfd3bc6..b7b98bf9e5 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -216,7 +216,7 @@ const config: Config = { position: 'left', }, { - to: 'docs/releases/v1.26.0', + to: 'docs/releases/v1.27.0', label: 'Releases', position: 'left', }, diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 6d9ba3a680..624fe83029 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,7 @@ { "releases": { "Release Notes": [ + "releases/v1.27.0", "releases/v1.26.0", "releases/v1.25.0", "releases/v1.24.0", From 9ee948a4d81a4c3d8a80783b471296aee69a6f9c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 15 May 2024 10:42:20 +0200 Subject: [PATCH 408/567] cli: bump esbuild to es2022 Signed-off-by: Patrik Oldsberg --- .changeset/young-camels-return.md | 5 +++++ packages/cli/src/lib/builder/config.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/young-camels-return.md diff --git a/.changeset/young-camels-return.md b/.changeset/young-camels-return.md new file mode 100644 index 0000000000..efdd822788 --- /dev/null +++ b/.changeset/young-camels-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Bump `esbuild` target for package builds to `ES2022`. diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 6fd92101f6..8beb2cbb1e 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -151,7 +151,7 @@ export async function makeRollupConfigs( template: svgrTemplate, }), esbuild({ - target: 'es2019', + target: 'ES2022', minify: options.minify, }), ], From 6accdd99c67fcbd5d3e5642051feba3c724537ce Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 15 May 2024 10:49:53 +0200 Subject: [PATCH 409/567] docs: hierarchy of domains Signed-off-by: Vincenzo Scamporlino --- .../software-catalog/software-model-entities.drawio.svg | 4 ++-- docs/features/software-catalog/system-model.md | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/assets/software-catalog/software-model-entities.drawio.svg b/docs/assets/software-catalog/software-model-entities.drawio.svg index d087583245..122f58b065 100644 --- a/docs/assets/software-catalog/software-model-entities.drawio.svg +++ b/docs/assets/software-catalog/software-model-entities.drawio.svg @@ -1,4 +1,4 @@ - + -
    Domain
    (e.g.  domain
    models, metrics, KPIs, business purpose)
    Domain...
    partOf
    partOf
    System
    Collection of entities that cooperate to perform some function
    System...
    dependsOn
    dependsOn
    partOf
    partOf
    Resource
    (e.g. SQL Database, S3 bucket, ...)
    Resource...
    consumesAPI
    consumesAPI
    API
    (e.g. OpenAPI, gRPC API, Avro, Dataset, ...)
    API...
    providesAPI
    providesAPI
    partOf
    partOf
    Component
    (e.g. backend service, data pipeline ...)
    Component...
    partOf
    partOf
    dependsOn
    dependsOn
    Kind: Group
    Kind: Group
    Kind: User
    Kind: User
    OwnerOf
    OwnerOf
    OwnedBy
    OwnedBy
    Service
    Service
    website
    website
    website
    website
    Types
    Types
    service
    service
    website
    website
    library
    library
    asyncapi
    asyncapi
    graphql
    graphql
    grpc
    grpc
    Types
    Types
    openapi
    openapi
    business-unit
    business-unit
    product-area
    product-area
    root
    root
    Types
    Types
    team
    team
    s3-bucket
    s3-bucket
    cluster
    cluster
    Types
    Types
    database
    database
    Kind: Template
    Parameters rendered in the
    frontend and the steps executed in  the scaffolding process
    Kind: Template...
    Kind: Location
    a marker that references other places to look for catalog data
    Kind: Location...
    memberOf
    memberOf
    hasMember
    hasMember
    parentOf
    parentOf
    childOf
    childOf
    Text is not SVG - cannot display
    \ No newline at end of file +
    Domain
    (e.g.  domain
    models, metrics, KPIs, business purpose)
    partOf
    System
    Collection of entities that cooperate to perform some function
    dependsOn
    partOf
    Resource
    (e.g. SQL Database, S3 bucket, ...)
    consumesAPI
    API
    (e.g. OpenAPI, gRPC API, Avro, Dataset, ...)
    providesAPI
    partOf
    Component
    (e.g. backend service, data pipeline ...)
    partOf
    dependsOn
    Kind: Group
    Kind: User
    OwnerOf
    OwnedBy
    Service
    website
    website
    Types
    service
    website
    library
    asyncapi
    graphql
    grpc
    Types
    openapi
    business-unit
    product-area
    root
    Types
    team
    s3-bucket
    cluster
    Types
    database
    Kind: Template
    Parameters rendered in the
    frontend and the steps executed in  the scaffolding process
    Kind: Location
    a marker that references other places to look for catalog data
    memberOf
    hasMember
    parentOf
    childOf
    subdomainOf
    \ No newline at end of file diff --git a/docs/features/software-catalog/system-model.md b/docs/features/software-catalog/system-model.md index af359bb18a..ee7270a960 100644 --- a/docs/features/software-catalog/system-model.md +++ b/docs/features/software-catalog/system-model.md @@ -120,6 +120,9 @@ product or use-case, share the same entity types in their APIs, and integrate well with each other. Other domains could be “Content Ingestion”, “Ads” or “Search”. +In case of a large organization, it might make sense to further group domains +in a hierarchy, where a domain can be a subdomain of another domain. + ## Other ### Location From 75b9c07da8761ea04b500a8754254c6bf386cc0d Mon Sep 17 00:00:00 2001 From: David Festal Date: Wed, 15 May 2024 11:42:47 +0200 Subject: [PATCH 410/567] Add a test for the support of external schemas in the router Signed-off-by: David Festal --- .../app-dir/dist/.config-schema.json | 4 + .../app-backend/src/service/router.test.ts | 75 ++++++++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 plugins/app-backend/src/service/__fixtures__/app-dir/dist/.config-schema.json diff --git a/plugins/app-backend/src/service/__fixtures__/app-dir/dist/.config-schema.json b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/.config-schema.json new file mode 100644 index 0000000000..3db59473d3 --- /dev/null +++ b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/.config-schema.json @@ -0,0 +1,4 @@ +{ + "backstageConfigSchemaVersion": 1, + "schemas": [] +} diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index 30fa5454c0..6fffdb149f 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -15,12 +15,13 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; +import { AppConfig, ConfigReader } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; import { resolve as resolvePath } from 'path'; import request from 'supertest'; import { createRouter } from './router'; +import { loadConfigSchema } from '@backstage/config-loader'; jest.mock('../lib/config', () => ({ injectConfig: jest.fn(), @@ -115,3 +116,75 @@ describe('createRouter with static fallback handler', () => { expect(response3.status).toBe(404); }); }); + +describe('createRouter config schema test', () => { + const libConfigs = require('../lib/config'); + const libConfigsActual = jest.requireActual('../lib/config'); + const readConfigsMock: jest.Mock = libConfigs.readConfigs; + + beforeEach(() => { + jest.resetAllMocks(); + readConfigsMock.mockImplementation(libConfigsActual.readConfigs); + }); + + it('uses an external schema', async () => { + await createRouter({ + logger: getVoidLogger(), + config: new ConfigReader({ + test: 'value', + }), + appPackageName: 'example-app', + schema: await loadConfigSchema({ + serialized: { + schemas: [ + { + value: { + type: 'object', + properties: { + test: { + visibility: 'frontend', + type: 'string', + }, + }, + }, + path: '/mock', + }, + ], + backstageConfigSchemaVersion: 1, + }, + }), + }); + + const results = readConfigsMock.mock.results; + expect(results.length).toBe(1); + + const mockedResult = results[0]; + expect(mockedResult.type).toBe('return'); + const result = await (mockedResult.value as Promise); + + expect(result.length).toBe(1); + expect(result[0].data).toStrictEqual({ + test: 'value', + }); + }); + + it('uses no external schema', async () => { + await createRouter({ + logger: getVoidLogger(), + config: new ConfigReader({ + test: 'value', + }), + appPackageName: 'example-app', + }); + + const results = readConfigsMock.mock.results; + expect(results.length).toBe(1); + + const mockedResult = results[0]; + expect(mockedResult.type).toBe('return'); + const result = await (mockedResult.value as Promise); + + expect(result.length).toBe(1); + expect(result[0].data).toStrictEqual({}); + }); +}); From fb398264c67c0a58f42ef512f74bf9c7a9a5f559 Mon Sep 17 00:00:00 2001 From: David Festal Date: Wed, 15 May 2024 11:43:39 +0200 Subject: [PATCH 411/567] Fix the regression in the support of external schemas in the router Signed-off-by: David Festal --- plugins/app-backend/src/service/router.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index f1fb20a4b5..8ec871df81 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -113,6 +113,7 @@ export async function createRouter( staticFallbackHandler, auth, httpAuth, + schema, } = options; const disableConfigInjection = @@ -143,6 +144,7 @@ export async function createRouter( config, appDistDir, env: process.env, + schema, }); const assetStore = From 82c2b908ccccb10c1865d2a4a9cfaac1bb63a543 Mon Sep 17 00:00:00 2001 From: David Festal Date: Wed, 15 May 2024 12:16:20 +0200 Subject: [PATCH 412/567] Add changeset Signed-off-by: David Festal --- .changeset/itchy-spoons-cry.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/itchy-spoons-cry.md diff --git a/.changeset/itchy-spoons-cry.md b/.changeset/itchy-spoons-cry.md new file mode 100644 index 0000000000..20877e8087 --- /dev/null +++ b/.changeset/itchy-spoons-cry.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Restore the support of external config schema in the router of the `app-backend` plugin, which was broken in release `1.26.0`. +This support is critical for dynamic frontend plugins to have access to their config values. From e74c615bd610b99931921bf4d94c6cbb4d97a4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 15 May 2024 13:04:54 +0200 Subject: [PATCH 413/567] remove last remnants of react-text-truncate from test mocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../Group/MembersList/MembersListCard.test.tsx | 13 ------------- .../Cards/OwnershipCard/OwnershipCard.test.tsx | 13 ------------- .../TechDocsSearchResultListItem.test.tsx | 5 ----- 3 files changed, 31 deletions(-) diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx index 06ec3b0ebd..793ffe274b 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -36,19 +36,6 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Observable } from '@backstage/types'; -// Mock needed because jsdom doesn't correctly implement box-sizing -// https://github.com/ShinyChang/React-Text-Truncate/issues/70 -// https://stackoverflow.com/questions/71916701/how-to-mock-a-react-function-component-that-takes-a-ref-prop -jest.mock('react-text-truncate', () => { - const { forwardRef } = jest.requireActual('react'); - return { - __esModule: true, - default: forwardRef((props: any, ref: any) => ( -

    - )), - }; -}); - const mockedStarredEntitiesApi: Partial = { starredEntitie$: () => { return { diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 72a912e16e..f2b949f768 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -120,19 +120,6 @@ const getEntitiesMock = ( } as GetEntitiesResponse); }; -// Mock needed because jsdom doesn't correctly implement box-sizing -// https://github.com/ShinyChang/React-Text-Truncate/issues/70 -// https://stackoverflow.com/questions/71916701/how-to-mock-a-react-function-component-that-takes-a-ref-prop -jest.mock('react-text-truncate', () => { - const { forwardRef } = jest.requireActual('react'); - return { - __esModule: true, - default: forwardRef((props: any, ref: any) => ( -
    {props.text}
    - )), - }; -}); - describe('OwnershipCard', () => { const groupEntity: GroupEntity = { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx index 15444fca9e..9ba88a78ff 100644 --- a/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx @@ -18,11 +18,6 @@ import React from 'react'; import { TechDocsSearchResultListItem } from './TechDocsSearchResultListItem'; import { renderInTestApp } from '@backstage/test-utils'; -// Using canvas to render text.. -jest.mock('react-text-truncate', () => { - return ({ text }: { text: string }) => {text}; -}); - const validResult = { location: 'https://backstage.io/docs', title: 'Documentation', From db7b8243a9fef10ca6090f641e0aa663ddc6a2d3 Mon Sep 17 00:00:00 2001 From: AmbrishRamachandiran Date: Wed, 15 May 2024 16:59:16 +0530 Subject: [PATCH 414/567] Updated documentation of auth providers Signed-off-by: AmbrishRamachandiran --- docs/auth/atlassian/provider.md | 6 +++++- docs/auth/gitlab/provider.md | 6 +++++- docs/auth/google/gcp-iap-auth.md | 6 +++++- docs/auth/google/provider.md | 6 +++++- docs/auth/microsoft/provider.md | 6 +++++- docs/auth/oauth2-proxy/provider.md | 6 +++++- docs/auth/okta/provider.md | 6 +++++- docs/auth/vmware-cloud/provider.md | 6 +++++- 8 files changed, 40 insertions(+), 8 deletions(-) diff --git a/docs/auth/atlassian/provider.md b/docs/auth/atlassian/provider.md index d6f19ee791..c8db009805 100644 --- a/docs/auth/atlassian/provider.md +++ b/docs/auth/atlassian/provider.md @@ -71,7 +71,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index ff6a341667..f5613582fd 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -70,7 +70,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index 709ef2b521..7e6178154c 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -50,7 +50,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `google.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. diff --git a/docs/auth/google/provider.md b/docs/auth/google/provider.md index f2d2e8524e..f7802d43f7 100644 --- a/docs/auth/google/provider.md +++ b/docs/auth/google/provider.md @@ -64,7 +64,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `google.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index ab3c5d8044..b96f175183 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -84,7 +84,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `microsoft.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. diff --git a/docs/auth/oauth2-proxy/provider.md b/docs/auth/oauth2-proxy/provider.md index aded90a1e7..8745f32297 100644 --- a/docs/auth/oauth2-proxy/provider.md +++ b/docs/auth/oauth2-proxy/provider.md @@ -40,7 +40,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `forwardedUserMatchingUserEntityName`: Matches the value in the `x-forwarded-user` header from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. diff --git a/docs/auth/okta/provider.md b/docs/auth/okta/provider.md index 0c15746cfd..6fe7a8b992 100644 --- a/docs/auth/okta/provider.md +++ b/docs/auth/okta/provider.md @@ -75,7 +75,11 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `okta.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. diff --git a/docs/auth/vmware-cloud/provider.md b/docs/auth/vmware-cloud/provider.md index d7580eccba..dd7c32f700 100644 --- a/docs/auth/vmware-cloud/provider.md +++ b/docs/auth/vmware-cloud/provider.md @@ -176,6 +176,10 @@ This provider includes several resolvers out of the box that you can use: - `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. - `vmwareCloudSignInResolvers`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will sign in the user without associating with a catalog user. -> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +:::note Note + +The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +::: If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. From 3e8148e39ce1a68dbd965584e8d1d32df2844144 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 14:49:40 +0200 Subject: [PATCH 415/567] feat: fix issue with redaction by implementing another way Signed-off-by: blam --- .../src/logging/WinstonLogger.test.ts | 2 +- .../src/logging/WinstonLogger.ts | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index 06f9cdd4c3..8f60e8dae2 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -80,7 +80,7 @@ describe('WinstonLogger', () => { nested: '[REDACTED] (world) from nested object', null: null, nullProto: { - foo: 'hello foo', // read only prop is not redacted + foo: '[REDACTED] foo', }, }, }), diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 3fcb4142b6..cb9d81cdf1 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -86,19 +86,17 @@ export class WinstonLogger implements RootLoggerService { let redactionPattern: RegExp | undefined = undefined; const replace = (obj: TransformableInfo) => { - for (const key in obj) { - if (Object.hasOwn(obj, key)) { - if (typeof obj[key] === 'object') { - obj[key] = replace(obj[key] as TransformableInfo); - } else if (typeof obj[key] === 'string') { - try { - obj[key] = obj[key]?.replace(redactionPattern, '[REDACTED]'); - } catch { - /* ignore read only properties */ - } - } + const stringifiedFields = JSON.stringify(obj, null); + const redacted = JSON.parse( + stringifiedFields.replace(redactionPattern!, '[REDACTED]'), + ); + + for (const key in redacted) { + if (obj && Object.hasOwn(obj, key)) { + obj[key] = redacted[key]; } } + return obj; }; return { From 7d30d95dee0174a8345883ad59704b60fadb2cd8 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 15:03:13 +0200 Subject: [PATCH 416/567] chore: safer way to do redactions Signed-off-by: blam --- .changeset/nine-ties-type.md | 6 ++++++ packages/backend-app-api/package.json | 2 ++ .../src/logging/WinstonLogger.ts | 9 ++++++-- plugins/scaffolder-backend/package.json | 2 ++ .../src/scaffolder/tasks/logger.ts | 21 ++++++++++++------- yarn.lock | 11 ++++++++++ 6 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 .changeset/nine-ties-type.md diff --git a/.changeset/nine-ties-type.md b/.changeset/nine-ties-type.md new file mode 100644 index 0000000000..abb0661724 --- /dev/null +++ b/.changeset/nine-ties-type.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-app-api': patch +--- + +Fixing issue with log meta fields possibly being circular refs diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 0634c30de1..b5fe3a1271 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -72,6 +72,7 @@ "fs-extra": "^11.2.0", "helmet": "^6.0.0", "jose": "^5.0.0", + "json-stringify-safe": "^5.0.1", "knex": "^3.0.0", "lodash": "^4.17.21", "logform": "^2.3.2", @@ -93,6 +94,7 @@ "@types/compression": "^1.7.0", "@types/fs-extra": "^11.0.0", "@types/http-errors": "^2.0.0", + "@types/json-stringify-safe": "^5.0.3", "@types/minimist": "^1.2.0", "@types/morgan": "^1.9.0", "@types/node-forge": "^1.3.0", diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index cb9d81cdf1..42c181573c 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -27,6 +27,7 @@ import { transports, transport as Transport, } from 'winston'; +import stringify from 'json-stringify-safe'; import { escapeRegExp } from '../lib/escapeRegExp'; /** @@ -86,9 +87,13 @@ export class WinstonLogger implements RootLoggerService { let redactionPattern: RegExp | undefined = undefined; const replace = (obj: TransformableInfo) => { - const stringifiedFields = JSON.stringify(obj, null); + if (!redactionPattern) { + return obj; + } + + const stringifiedFields = stringify(obj); const redacted = JSON.parse( - stringifiedFields.replace(redactionPattern!, '[REDACTED]'), + stringifiedFields.replace(redactionPattern, '[REDACTED]'), ); for (const key in redacted) { diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f2f4da6a84..877163a151 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -85,6 +85,7 @@ "globby": "^11.0.0", "isbinaryfile": "^5.0.0", "isolated-vm": "^4.5.0", + "json-stringify-safe": "^5.0.1", "jsonschema": "^1.2.6", "knex": "^3.0.0", "lodash": "^4.17.21", @@ -107,6 +108,7 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@types/fs-extra": "^11.0.0", + "@types/json-stringify-safe": "^5.0.3", "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", "@types/zen-observable": "^0.8.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 064eb69349..01f454a6ff 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -21,6 +21,7 @@ import { JsonObject } from '@backstage/types'; import { Format, TransformableInfo } from 'logform'; import Transport, { TransportStreamOptions } from 'winston-transport'; import { Logger, format, createLogger, transports } from 'winston'; +import stringify from 'json-stringify-safe'; /** * Escapes a given string to be used inside a RegExp. @@ -108,15 +109,21 @@ export class WinstonLogger implements RootLoggerService { let redactionPattern: RegExp | undefined = undefined; const replace = (obj: TransformableInfo) => { - for (const key in obj) { - if (obj.hasOwnProperty(key)) { - if (typeof obj[key] === 'object') { - obj[key] = replace(obj[key] as TransformableInfo); - } else if (typeof obj[key] === 'string') { - obj[key] = obj[key]?.replace(redactionPattern, '[REDACTED]'); - } + if (!redactionPattern) { + return obj; + } + + const stringifiedFields = stringify(obj); + const redacted = JSON.parse( + stringifiedFields.replace(redactionPattern, '[REDACTED]'), + ); + + for (const key in redacted) { + if (obj && Object.hasOwn(obj, key)) { + obj[key] = redacted[key]; } } + return obj; }; return { diff --git a/yarn.lock b/yarn.lock index f10de89d74..eccfdd3eb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3304,6 +3304,7 @@ __metadata: "@types/express": ^4.17.6 "@types/fs-extra": ^11.0.0 "@types/http-errors": ^2.0.0 + "@types/json-stringify-safe": ^5.0.3 "@types/minimist": ^1.2.0 "@types/morgan": ^1.9.0 "@types/node-forge": ^1.3.0 @@ -3317,6 +3318,7 @@ __metadata: helmet: ^6.0.0 http-errors: ^2.0.0 jose: ^5.0.0 + json-stringify-safe: ^5.0.1 knex: ^3.0.0 lodash: ^4.17.21 logform: ^2.3.2 @@ -6661,6 +6663,7 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/fs-extra": ^11.0.0 + "@types/json-stringify-safe": ^5.0.3 "@types/luxon": ^3.0.0 "@types/nunjucks": ^3.1.4 "@types/supertest": ^2.0.8 @@ -6673,6 +6676,7 @@ __metadata: globby: ^11.0.0 isbinaryfile: ^5.0.0 isolated-vm: ^4.5.0 + json-stringify-safe: ^5.0.1 jsonschema: ^1.2.6 knex: ^3.0.0 lodash: ^4.17.21 @@ -17312,6 +17316,13 @@ __metadata: languageName: node linkType: hard +"@types/json-stringify-safe@npm:^5.0.3": + version: 5.0.3 + resolution: "@types/json-stringify-safe@npm:5.0.3" + checksum: 66826a59b53ce5a5becc9c05cd0bdadb7e2032e02803a9dd13137de134a0882a6c67ae334371ccff07a20b1d2b2c23fa3a81b5e23f40b27cfcf553c7cce06e35 + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" From 0f68c5096db97136d8c8591f49440f7b2af2965b Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 16:24:23 +0200 Subject: [PATCH 417/567] chore: rework to use the message symbol instead Signed-off-by: blam --- packages/backend-app-api/package.json | 3 +- .../src/logging/WinstonLogger.test.ts | 97 ++++++++++--------- .../src/logging/WinstonLogger.ts | 33 +++---- 3 files changed, 65 insertions(+), 68 deletions(-) diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index b5fe3a1271..414a4333e7 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -72,7 +72,6 @@ "fs-extra": "^11.2.0", "helmet": "^6.0.0", "jose": "^5.0.0", - "json-stringify-safe": "^5.0.1", "knex": "^3.0.0", "lodash": "^4.17.21", "logform": "^2.3.2", @@ -84,6 +83,7 @@ "path-to-regexp": "^6.2.1", "selfsigned": "^2.0.0", "stoppable": "^1.1.0", + "triple-beam": "^1.4.1", "uuid": "^9.0.0", "winston": "^3.2.1", "winston-transport": "^4.5.0" @@ -94,7 +94,6 @@ "@types/compression": "^1.7.0", "@types/fs-extra": "^11.0.0", "@types/http-errors": "^2.0.0", - "@types/json-stringify-safe": "^5.0.3", "@types/minimist": "^1.2.0", "@types/morgan": "^1.9.0", "@types/node-forge": "^1.3.0", diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index 8f60e8dae2..4b608f8e57 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -14,12 +14,10 @@ * limitations under the License. */ -import { TransformableInfo } from 'logform'; +import { format } from 'logform'; import { WinstonLogger } from './WinstonLogger'; - -function msg(info: TransformableInfo): TransformableInfo { - return { message: info.message, level: info.level, stack: info.stack }; -} +import Transport from 'winston-transport'; +import { MESSAGE } from 'triple-beam'; describe('WinstonLogger', () => { it('creates a winston logger instance with default options', () => { @@ -33,57 +31,66 @@ describe('WinstonLogger', () => { expect(childLogger).toBeInstanceOf(WinstonLogger); }); - it('redacter should redact and escape regex', () => { - const redacter = WinstonLogger.redacter(); - const log = { - level: 'error', - message: 'hello (world)', - stack: 'hello (world) from this file', - }; - expect(redacter.format.transform(msg(log))).toEqual(msg(log)); - redacter.add(['hello\n']); - expect(redacter.format.transform(msg(log))).toEqual( - msg({ - ...log, - message: '[REDACTED] (world)', - stack: '[REDACTED] (world) from this file', - }), - ); - redacter.add(['(world']); - expect(redacter.format.transform(msg(log))).toEqual( - msg({ - ...log, - message: '[REDACTED] [REDACTED])', - stack: '[REDACTED] [REDACTED]) from this file', + it('should redact and escape regex', () => { + const mockTransport = new Transport({ + log: jest.fn(), + logv: jest.fn(), + }); + + const logger = WinstonLogger.create({ + format: format.json(), + transports: [mockTransport], + }); + + logger.addRedactions(['hello (world']); + + logger.error('hello (world) from this file'); + + expect(mockTransport.log).toHaveBeenCalledWith( + expect.objectContaining({ + [MESSAGE]: JSON.stringify({ + level: 'error', + message: '[REDACTED]) from this file', + }), }), + expect.any(Function), ); }); - it('redacter should redact nested object', () => { - const redacter = WinstonLogger.redacter(); - const log = { - level: 'error', - message: { - nested: 'hello (world) from nested object', - null: null, - nullProto: Object.create(null, { - foo: { value: 'hello foo', enumerable: true }, - }), - }, - }; + it('should redact nested object', () => { + const mockTransport = new Transport({ + log: jest.fn(), + logv: jest.fn(), + }); - redacter.add(['hello']); - expect(redacter.format.transform(msg(log))).toEqual( - msg({ - ...log, - message: { + const logger = WinstonLogger.create({ + format: format.json(), + transports: [mockTransport], + }); + + logger.addRedactions(['hello']); + + logger.error('something went wrong', { + null: null, + nested: 'hello (world) from nested object', + nullProto: Object.create(null, { + foo: { value: 'hello foo', enumerable: true }, + }), + }); + + expect(mockTransport.log).toHaveBeenCalledWith( + expect.objectContaining({ + [MESSAGE]: JSON.stringify({ + level: 'error', + message: 'something went wrong', nested: '[REDACTED] (world) from nested object', null: null, nullProto: { foo: '[REDACTED] foo', }, - }, + }), }), + expect.any(Function), ); }); }); diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 42c181573c..3133fde949 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -27,7 +27,7 @@ import { transports, transport as Transport, } from 'winston'; -import stringify from 'json-stringify-safe'; +import { MESSAGE } from 'triple-beam'; import { escapeRegExp } from '../lib/escapeRegExp'; /** @@ -62,8 +62,8 @@ export class WinstonLogger implements RootLoggerService { let logger = createLogger({ level: process.env.LOG_LEVEL || options.level || 'info', format: format.combine( - redacter.format, options.format ?? defaultFormatter, + redacter.format, ), transports: options.transports ?? new transports.Console(), }); @@ -86,26 +86,17 @@ export class WinstonLogger implements RootLoggerService { let redactionPattern: RegExp | undefined = undefined; - const replace = (obj: TransformableInfo) => { - if (!redactionPattern) { - return obj; - } - - const stringifiedFields = stringify(obj); - const redacted = JSON.parse( - stringifiedFields.replace(redactionPattern, '[REDACTED]'), - ); - - for (const key in redacted) { - if (obj && Object.hasOwn(obj, key)) { - obj[key] = redacted[key]; - } - } - - return obj; - }; return { - format: format(replace)(), + format: format((obj: TransformableInfo) => { + if (!redactionPattern || !obj) { + return obj; + } + + obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '[REDACTED]'); + obj.message = obj.message.replace?.(redactionPattern, '[REDACTED]'); + + return obj; + })(), add(newRedactions) { let added = 0; for (const redactionToTrim of newRedactions) { From d6c34ab98f8f219ef480435ffd23ecb22e4e4a7b Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 16:30:22 +0200 Subject: [PATCH 418/567] chore: update in the scaffolder instead Signed-off-by: blam --- plugins/scaffolder-backend/package.json | 2 +- .../src/scaffolder/tasks/logger.ts | 31 +++++++------------ yarn.lock | 20 ++++-------- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 877163a151..59c9901eb8 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -96,6 +96,7 @@ "p-queue": "^6.6.2", "prom-client": "^15.0.0", "tar": "^6.1.12", + "triple-beam": "^1.4.1", "uuid": "^9.0.0", "winston": "^3.2.1", "winston-transport": "^4.7.0", @@ -108,7 +109,6 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@types/fs-extra": "^11.0.0", - "@types/json-stringify-safe": "^5.0.3", "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", "@types/zen-observable": "^0.8.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 01f454a6ff..4314f39385 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -21,7 +21,7 @@ import { JsonObject } from '@backstage/types'; import { Format, TransformableInfo } from 'logform'; import Transport, { TransportStreamOptions } from 'winston-transport'; import { Logger, format, createLogger, transports } from 'winston'; -import stringify from 'json-stringify-safe'; +import { MESSAGE } from 'triple-beam'; /** * Escapes a given string to be used inside a RegExp. @@ -108,26 +108,17 @@ export class WinstonLogger implements RootLoggerService { let redactionPattern: RegExp | undefined = undefined; - const replace = (obj: TransformableInfo) => { - if (!redactionPattern) { - return obj; - } - - const stringifiedFields = stringify(obj); - const redacted = JSON.parse( - stringifiedFields.replace(redactionPattern, '[REDACTED]'), - ); - - for (const key in redacted) { - if (obj && Object.hasOwn(obj, key)) { - obj[key] = redacted[key]; - } - } - - return obj; - }; return { - format: format(replace)(), + format: format((obj: TransformableInfo) => { + if (!redactionPattern || !obj) { + return obj; + } + + obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '[REDACTED]'); + obj.message = obj.message?.replace?.(redactionPattern, '[REDACTED]'); + + return obj; + })(), add(newRedactions) { let added = 0; for (const redactionToTrim of newRedactions) { diff --git a/yarn.lock b/yarn.lock index eccfdd3eb5..d85a001f17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3304,7 +3304,6 @@ __metadata: "@types/express": ^4.17.6 "@types/fs-extra": ^11.0.0 "@types/http-errors": ^2.0.0 - "@types/json-stringify-safe": ^5.0.3 "@types/minimist": ^1.2.0 "@types/morgan": ^1.9.0 "@types/node-forge": ^1.3.0 @@ -3318,7 +3317,6 @@ __metadata: helmet: ^6.0.0 http-errors: ^2.0.0 jose: ^5.0.0 - json-stringify-safe: ^5.0.1 knex: ^3.0.0 lodash: ^4.17.21 logform: ^2.3.2 @@ -3332,6 +3330,7 @@ __metadata: selfsigned: ^2.0.0 stoppable: ^1.1.0 supertest: ^6.1.3 + triple-beam: ^1.4.1 uuid: ^9.0.0 winston: ^3.2.1 winston-transport: ^4.5.0 @@ -6663,7 +6662,6 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/fs-extra": ^11.0.0 - "@types/json-stringify-safe": ^5.0.3 "@types/luxon": ^3.0.0 "@types/nunjucks": ^3.1.4 "@types/supertest": ^2.0.8 @@ -6689,6 +6687,7 @@ __metadata: strip-ansi: ^7.1.0 supertest: ^6.1.3 tar: ^6.1.12 + triple-beam: ^1.4.1 uuid: ^9.0.0 wait-for-expect: ^3.0.2 winston: ^3.2.1 @@ -17316,13 +17315,6 @@ __metadata: languageName: node linkType: hard -"@types/json-stringify-safe@npm:^5.0.3": - version: 5.0.3 - resolution: "@types/json-stringify-safe@npm:5.0.3" - checksum: 66826a59b53ce5a5becc9c05cd0bdadb7e2032e02803a9dd13137de134a0882a6c67ae334371ccff07a20b1d2b2c23fa3a81b5e23f40b27cfcf553c7cce06e35 - languageName: node - linkType: hard - "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -41491,10 +41483,10 @@ __metadata: languageName: node linkType: hard -"triple-beam@npm:^1.3.0": - version: 1.3.0 - resolution: "triple-beam@npm:1.3.0" - checksum: 7d7b77d8625fb252c126c24984a68de462b538a8fcd1de2abd0a26421629cf3527d48e23b3c2264f08f4a6c3bc40a478a722176f4d7b6a1acc154cb70c359f2b +"triple-beam@npm:^1.3.0, triple-beam@npm:^1.4.1": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 2e881a3e8e076b6f2b85b9ec9dd4a900d3f5016e6d21183ed98e78f9abcc0149e7d54d79a3f432b23afde46b0885bdcdcbff789f39bc75de796316961ec07f61 languageName: node linkType: hard From 2bbc55f0a8a1938146fcd1a3466bacae4dbf7e95 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 16:53:18 +0200 Subject: [PATCH 419/567] chore: leave message clean Signed-off-by: blam --- packages/backend-app-api/src/logging/WinstonLogger.ts | 1 - plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 3133fde949..8ee86d247f 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -93,7 +93,6 @@ export class WinstonLogger implements RootLoggerService { } obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '[REDACTED]'); - obj.message = obj.message.replace?.(redactionPattern, '[REDACTED]'); return obj; })(), diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 4314f39385..3824a32a6f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -115,7 +115,6 @@ export class WinstonLogger implements RootLoggerService { } obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '[REDACTED]'); - obj.message = obj.message?.replace?.(redactionPattern, '[REDACTED]'); return obj; })(), From c0a287a79b9f802d754e3570526ae4df8e1921d2 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 15 May 2024 16:45:55 +0200 Subject: [PATCH 420/567] refactor: extract `isDatabaseConflictError` to plugin api Signed-off-by: Camila Belo --- packages/backend-common/api-report.md | 5 +++-- packages/backend-common/src/database/index.ts | 1 - packages/backend-common/src/deprecated/index.ts | 14 +++++++++++--- packages/backend-plugin-api/api-report.md | 3 +++ .../backend-plugin-api/src/services/index.ts | 1 + .../src/services/utilities/database.ts} | 0 .../src/services/utilities/index.ts | 17 +++++++++++++++++ .../src/database/DefaultProviderDatabase.ts | 6 ++++-- .../catalog-backend/src/database/conversion.ts | 2 +- .../refreshState/insertUnprocessedEntity.ts | 6 ++++-- 10 files changed, 44 insertions(+), 11 deletions(-) rename packages/{backend-common/src/database/util.ts => backend-plugin-api/src/services/utilities/database.ts} (100%) create mode 100644 packages/backend-plugin-api/src/services/utilities/index.ts diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 42df32dc1d..0d1655be28 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -35,6 +35,7 @@ import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; import { isChildPath as isChildPath_2 } from '@backstage/backend-plugin-api'; +import { isDatabaseConflictError as isDatabaseConflictError_2 } from '@backstage/backend-plugin-api'; import { KubeConfig } from '@kubernetes/client-node'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoadConfigOptionsRemote } from '@backstage/config-loader'; @@ -538,8 +539,8 @@ export const HostDiscovery: typeof HostDiscovery_2; // @public @deprecated (undocumented) export const isChildPath: typeof isChildPath_2; -// @public -export function isDatabaseConflictError(e: unknown): boolean; +// @public @deprecated (undocumented) +export const isDatabaseConflictError: typeof isDatabaseConflictError_2; // @public export class KubernetesContainerRunner implements ContainerRunner { diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index a0a61ccdd0..77d75653b8 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -21,4 +21,3 @@ export type { } from './DatabaseManager'; export type { PluginDatabaseManager } from './types'; -export { isDatabaseConflictError } from './util'; diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts index 708e358bfc..d9ca795c3a 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -17,6 +17,7 @@ export * from './scm'; import { + isDatabaseConflictError as _isDatabaseConflictError, resolvePackagePath as _resolvePackagePath, resolveSafeChildPath as _resolveSafeChildPath, isChildPath as _isChildPath, @@ -24,21 +25,28 @@ import { /** * @public - * @deprecated This type is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * @deprecated This function is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. + * Please use the `isDatabaseConflictError` function from the `@backstage/backend-plugin-api` package instead. + */ +export const isDatabaseConflictError = _isDatabaseConflictError; + +/** + * @public + * @deprecated This function is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. * Please use the `resolvePackagePath` function from the `@backstage/backend-plugin-api` package instead. */ export const resolvePackagePath = _resolvePackagePath; /** * @public - * @deprecated This type is deprecated and will be removed in a future release, see + * @deprecated This function is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. * Please use the `resolveSafeChildPath` function from the `@backstage/backend-plugin-api` package instead. */ export const resolveSafeChildPath = _resolveSafeChildPath; /** * @public - * @deprecated This type is deprecated and will be removed in a future release, see + * @deprecated This function is deprecated and will be removed in a future release, see https://github.com/backstage/backstage/issues/24493. * Please use the `isChildPath` function from the `@backstage/cli-common` package instead. */ export const isChildPath = _isChildPath; diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 932ccc74ab..860907c12c 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -354,6 +354,9 @@ export interface IdentityService extends IdentityApi {} export { isChildPath }; +// @public +export function isDatabaseConflictError(e: unknown): boolean; + // @public (undocumented) export interface LifecycleService { addShutdownHook( diff --git a/packages/backend-plugin-api/src/services/index.ts b/packages/backend-plugin-api/src/services/index.ts index dd24127b88..01d3da444b 100644 --- a/packages/backend-plugin-api/src/services/index.ts +++ b/packages/backend-plugin-api/src/services/index.ts @@ -16,3 +16,4 @@ export * from './definitions'; export * from './system'; +export * from './utilities'; diff --git a/packages/backend-common/src/database/util.ts b/packages/backend-plugin-api/src/services/utilities/database.ts similarity index 100% rename from packages/backend-common/src/database/util.ts rename to packages/backend-plugin-api/src/services/utilities/database.ts diff --git a/packages/backend-plugin-api/src/services/utilities/index.ts b/packages/backend-plugin-api/src/services/utilities/index.ts new file mode 100644 index 0000000000..0eec4f3e84 --- /dev/null +++ b/packages/backend-plugin-api/src/services/utilities/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { isDatabaseConflictError } from './database'; diff --git a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts index 5b016438e3..94026fbfa0 100644 --- a/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProviderDatabase.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { isDatabaseConflictError } from '@backstage/backend-common'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { DeferredEntity } from '@backstage/plugin-catalog-node'; import { Knex } from 'knex'; @@ -34,7 +33,10 @@ import { Transaction, } from './types'; import { generateStableHash } from './util'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + isDatabaseConflictError, +} from '@backstage/backend-plugin-api'; // The number of items that are sent per batch to the database layer, when // doing .batchInsert calls to knex. This needs to be low enough to not cause diff --git a/plugins/catalog-backend/src/database/conversion.ts b/plugins/catalog-backend/src/database/conversion.ts index 827ae9b367..32de149572 100644 --- a/plugins/catalog-backend/src/database/conversion.ts +++ b/plugins/catalog-backend/src/database/conversion.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { isDatabaseConflictError } from '@backstage/backend-common'; +import { isDatabaseConflictError } from '@backstage/backend-plugin-api'; import { ConflictError, InputError } from '@backstage/errors'; import { DateTime } from 'luxon'; diff --git a/plugins/catalog-backend/src/database/operations/refreshState/insertUnprocessedEntity.ts b/plugins/catalog-backend/src/database/operations/refreshState/insertUnprocessedEntity.ts index d6309c27fc..195dbe1f2c 100644 --- a/plugins/catalog-backend/src/database/operations/refreshState/insertUnprocessedEntity.ts +++ b/plugins/catalog-backend/src/database/operations/refreshState/insertUnprocessedEntity.ts @@ -18,8 +18,10 @@ import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { Knex } from 'knex'; import { DbRefreshStateRow } from '../../tables'; import { v4 as uuid } from 'uuid'; -import { isDatabaseConflictError } from '@backstage/backend-common'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + isDatabaseConflictError, +} from '@backstage/backend-plugin-api'; /** * Attempts to insert a new refresh state row for the given entity, returning From 5c11b14f9daa4386b5a1ce7ef01246f7e7ce430b Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Wed, 15 May 2024 20:42:37 +0530 Subject: [PATCH 421/567] updated note in documentation Signed-off-by: JeevaRamanathan --- docs/integrations/github/org.md | 10 +++++++--- docs/integrations/gitlab/discovery.md | 6 +++++- docs/integrations/gitlab/org.md | 6 +++++- docs/integrations/ldap/org.md | 8 ++++++-- docs/permissions/getting-started.md | 6 +++++- .../04-authorizing-access-to-paginated-data.md | 6 +++++- .../plugin-authors/05-frontend-authorization.md | 6 +++++- docs/plugins/backend-plugin.md | 6 +++++- 8 files changed, 43 insertions(+), 11 deletions(-) diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 06b4e07daa..f94ffe6c7a 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -17,9 +17,13 @@ is a hierarchy of [`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind entities that mirror your org setup. -> Note: This adds `User` and `Group` entities to the catalog, but does not -> provide authentication. See the -> [GitHub auth provider](../../auth/github/provider.md) for that. +:::note Note + +This adds `User` and `Group` entities to the catalog, but does not +provide authentication. See the +[GitHub auth provider](../../auth/github/provider.md) for that. + +::: ## Permissions diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 91efe05fee..b16f6db839 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -136,7 +136,11 @@ To use the discovery provider, you'll need a GitLab integration [set up](locations.md) with a `token`. Then you can add a provider config per group to the catalog configuration. -> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +:::note Note + +If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. + +::: ```yaml title="app-config.yaml" catalog: diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index 14587461e5..2cc0dcfb82 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -158,7 +158,11 @@ amount of data, this can take significant time and resources. The token used must have the `read_api` scope, and the Users and Groups fetched will be those visible to the account which provisioned the token. -> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +:::note Note + +If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. + +::: ```yaml catalog: diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 49a2776fb1..a10bc3918c 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -29,8 +29,12 @@ to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap ``` -> Note: When configuring to use a Provider instead of a Processor you do not -> need to add a _location_ pointing to your LDAP server +:::note Note + +When configuring to use a Provider instead of a Processor you do not +need to add a _location_ pointing to your LDAP server + +::: Update the catalog plugin initialization in your backend to add the provider and schedule it: diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index e241f0e38e..4dabf3fcbd 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -8,7 +8,11 @@ If you prefer to watch a video instead, you can start with this video introducti -> Note: This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. +:::note Note + +This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. + +::: Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index a9e62040c5..84e86140fc 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -36,7 +36,11 @@ This approach will work for simple cases, but it has a downside: it forces us to To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. -> Note: in order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. +:::note Note + +In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. + +::: ## Creating the read permission diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index 92d855698f..60458aaf4d 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -8,7 +8,11 @@ In the previous sections, we learned how to protect our plugin's backend API rou Take, for example, the "Add" button in our todo list application. When a user clicks this button, the frontend makes a `POST` request to the `/todos` route of our backend. If a user tries to add a todo but is not authorized, they will have no way of knowing this until they perform the action and are faced with an error. This is a poor user experience. We can do better by disabling the add button. -> Note: Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component. +:::note Note + +Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component. + +::: ## Using `usePermission` diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 7a51cb635c..255ab4f865 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -44,7 +44,11 @@ cd plugins/carmen-backend yarn start ``` -> Note: this documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) +:::note Note + +This documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) + +::: This will think for a bit, and then say `Listening on :7007`. In a different terminal window, now run From 57a1d69e1df49953a9a47d46431409fa25d19c37 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 17:23:05 +0200 Subject: [PATCH 422/567] chore: go away dependency Signed-off-by: blam --- plugins/scaffolder-backend/package.json | 1 - yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 59c9901eb8..f748ccb92d 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -85,7 +85,6 @@ "globby": "^11.0.0", "isbinaryfile": "^5.0.0", "isolated-vm": "^4.5.0", - "json-stringify-safe": "^5.0.1", "jsonschema": "^1.2.6", "knex": "^3.0.0", "lodash": "^4.17.21", diff --git a/yarn.lock b/yarn.lock index d85a001f17..728bac0c68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6674,7 +6674,6 @@ __metadata: globby: ^11.0.0 isbinaryfile: ^5.0.0 isolated-vm: ^4.5.0 - json-stringify-safe: ^5.0.1 jsonschema: ^1.2.6 knex: ^3.0.0 lodash: ^4.17.21 From 0f603c8a1c8dae606c2302d362aff36a9c8dd188 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 17:26:33 +0200 Subject: [PATCH 423/567] chore: [REDACTED] -> *** Signed-off-by: blam Signed-off-by: blam --- packages/backend-app-api/src/logging/WinstonLogger.test.ts | 6 +++--- packages/backend-app-api/src/logging/WinstonLogger.ts | 2 +- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 5 +---- plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index 4b608f8e57..0f9e079943 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -50,7 +50,7 @@ describe('WinstonLogger', () => { expect.objectContaining({ [MESSAGE]: JSON.stringify({ level: 'error', - message: '[REDACTED]) from this file', + message: '***) from this file', }), }), expect.any(Function), @@ -83,10 +83,10 @@ describe('WinstonLogger', () => { [MESSAGE]: JSON.stringify({ level: 'error', message: 'something went wrong', - nested: '[REDACTED] (world) from nested object', + nested: '*** (world) from nested object', null: null, nullProto: { - foo: '[REDACTED] foo', + foo: '*** foo', }, }), }), diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 8ee86d247f..64e97b230c 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -92,7 +92,7 @@ export class WinstonLogger implements RootLoggerService { return obj; } - obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '[REDACTED]'); + obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***'); return obj; })(), diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 77097e97ea..6ae01c60cd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -272,10 +272,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { if (task.isDryRun) { const redactedSecrets = Object.fromEntries( - Object.entries(task.secrets ?? {}).map(secret => [ - secret[0], - '[REDACTED]', - ]), + Object.entries(task.secrets ?? {}).map(secret => [secret[0], '***']), ); const debugInput = (step.input && diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 3824a32a6f..e85c61d489 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -114,7 +114,7 @@ export class WinstonLogger implements RootLoggerService { return obj; } - obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '[REDACTED]'); + obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***'); return obj; })(), From d617103be77e5b076266eb6b094f6d6721253517 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 15 May 2024 17:27:19 +0200 Subject: [PATCH 424/567] chore: adde changeset Signed-off-by: blam --- .changeset/smooth-gifts-nail.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/smooth-gifts-nail.md diff --git a/.changeset/smooth-gifts-nail.md b/.changeset/smooth-gifts-nail.md new file mode 100644 index 0000000000..aa2585bc4f --- /dev/null +++ b/.changeset/smooth-gifts-nail.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-app-api': patch +--- + +Updating the logger redaction message to something less dramatic From 5ce928b93a09271ea69f6e978411d45a031ce313 Mon Sep 17 00:00:00 2001 From: Brian Phillips <28457+brianphillips@users.noreply.github.com> Date: Wed, 15 May 2024 11:00:33 -0500 Subject: [PATCH 425/567] Split search index service startup into a build and start phase Fixes #24794. The build process takes place synchronously to the plugin initialization rather than at startup so that the HTTP router has the relevant types available to validate incoming requests. Signed-off-by: Brian Phillips <28457+brianphillips@users.noreply.github.com> --- plugins/search-backend-node/src/alpha.ts | 21 ++++++++++++++++----- plugins/search-backend/src/alpha.ts | 11 ++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/plugins/search-backend-node/src/alpha.ts b/plugins/search-backend-node/src/alpha.ts index 331f0ae304..20b0f22089 100644 --- a/plugins/search-backend-node/src/alpha.ts +++ b/plugins/search-backend-node/src/alpha.ts @@ -34,7 +34,7 @@ import { * @alpha * Options for build method on {@link SearchIndexService}. */ -export type SearchIndexServiceStartOptions = { +export type SearchIndexServiceBuildOptions = { searchEngine: SearchEngine; collators: RegisterCollatorParameters[]; decorators: RegisterDecoratorParameters[]; @@ -45,15 +45,21 @@ export type SearchIndexServiceStartOptions = { * Interface for implementation of index service. */ export interface SearchIndexService { + /** + * Initializes state in preparation for starting the search index service + */ + build(options: SearchIndexServiceBuildOptions): void; + /** * Starts indexing process */ - start(options: SearchIndexServiceStartOptions): Promise; + start(): Promise; /** * Stops indexing process */ stop(): Promise; + /** * Returns an index types list. */ @@ -83,7 +89,7 @@ type DefaultSearchIndexServiceOptions = { /** * @alpha - * Reponsible for register the indexing task and start the schedule. + * Responsible for register the indexing task and start the schedule. */ class DefaultSearchIndexService implements SearchIndexService { private readonly logger: LoggerService; @@ -98,7 +104,7 @@ class DefaultSearchIndexService implements SearchIndexService { return new DefaultSearchIndexService(options); } - async start(options: SearchIndexServiceStartOptions): Promise { + build(options: SearchIndexServiceBuildOptions): void { this.indexBuilder = new IndexBuilder({ logger: this.logger, searchEngine: options.searchEngine, @@ -111,8 +117,13 @@ class DefaultSearchIndexService implements SearchIndexService { options.decorators.forEach(decorator => this.indexBuilder?.addDecorator(decorator), ); + } - const { scheduler } = await this.indexBuilder?.build(); + async start(): Promise { + if (!this.indexBuilder) { + throw new Error('IndexBuilder is not initialized, call build first'); + } + const { scheduler } = await this.indexBuilder.build(); this.scheduler = scheduler; this.scheduler!.start(); } diff --git a/plugins/search-backend/src/alpha.ts b/plugins/search-backend/src/alpha.ts index a17b542903..a7c2c6c872 100644 --- a/plugins/search-backend/src/alpha.ts +++ b/plugins/search-backend/src/alpha.ts @@ -121,13 +121,14 @@ export default createBackendPlugin({ const collators = searchIndexRegistry.getCollators(); const decorators = searchIndexRegistry.getDecorators(); + searchIndexService.build({ + searchEngine: searchEngine!, + collators, + decorators, + }); lifecycle.addStartupHook(async () => { - await searchIndexService.start({ - searchEngine: searchEngine!, - collators, - decorators, - }); + await searchIndexService.start(); }); lifecycle.addShutdownHook(async () => { From 5b6f979d8608ab80b61a71924403c3848c5c9c35 Mon Sep 17 00:00:00 2001 From: Brian Phillips <28457+brianphillips@users.noreply.github.com> Date: Wed, 15 May 2024 11:04:36 -0500 Subject: [PATCH 426/567] Add changeset Signed-off-by: Brian Phillips <28457+brianphillips@users.noreply.github.com> --- .changeset/seven-geese-raise.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/seven-geese-raise.md diff --git a/.changeset/seven-geese-raise.md b/.changeset/seven-geese-raise.md new file mode 100644 index 0000000000..176f6ae479 --- /dev/null +++ b/.changeset/seven-geese-raise.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-search-backend': patch +--- + +Split backend search plugin startup into "build" and "start" stages to ensure necessary initialization has happened before startup From c31ad6bee331cba5ca1e2671f5777f873ed86295 Mon Sep 17 00:00:00 2001 From: Brian Phillips <28457+brianphillips@users.noreply.github.com> Date: Wed, 15 May 2024 11:25:48 -0500 Subject: [PATCH 427/567] update API reports for search-backend-node Signed-off-by: Brian Phillips <28457+brianphillips@users.noreply.github.com> --- plugins/search-backend-node/api-report-alpha.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/search-backend-node/api-report-alpha.md b/plugins/search-backend-node/api-report-alpha.md index 8e35e70f65..4c1e70b32b 100644 --- a/plugins/search-backend-node/api-report-alpha.md +++ b/plugins/search-backend-node/api-report-alpha.md @@ -32,20 +32,21 @@ export const searchIndexRegistryExtensionPoint: ExtensionPoint; - start(options: SearchIndexServiceStartOptions): Promise; + start(): Promise; stop(): Promise; } // @alpha -export const searchIndexServiceRef: ServiceRef; - -// @alpha -export type SearchIndexServiceStartOptions = { +export type SearchIndexServiceBuildOptions = { searchEngine: SearchEngine; collators: RegisterCollatorParameters[]; decorators: RegisterDecoratorParameters[]; }; +// @alpha +export const searchIndexServiceRef: ServiceRef; + // (No @packageDocumentation comment for this package) ``` From 3dca9cbe7b817bc2cc4e68f44f0a177b611a887c Mon Sep 17 00:00:00 2001 From: erik-adsk <143032411+erik-adsk@users.noreply.github.com> Date: Wed, 15 May 2024 09:55:32 -0700 Subject: [PATCH 428/567] Update .changeset description to be more succinct Co-authored-by: Ben Lambert Signed-off-by: erik-adsk <143032411+erik-adsk@users.noreply.github.com> --- .changeset/loud-pumpkins-bow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/loud-pumpkins-bow.md b/.changeset/loud-pumpkins-bow.md index 366645db24..a55c97e19a 100644 --- a/.changeset/loud-pumpkins-bow.md +++ b/.changeset/loud-pumpkins-bow.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-react': patch --- -Fixed a bug where links in scaffolder field markdown content e.g. - description was not able to be launched in a new tab. This fix makes it so that all links within Scaffolder Fields are launched in a new tab. +Links that are rendered in the markdown in the `ScaffolderField` component are now opened in new tabs. From 612a45372e8dc599486c3b1076614b9a1ed1b573 Mon Sep 17 00:00:00 2001 From: vinisdl Date: Wed, 15 May 2024 14:42:29 -0300 Subject: [PATCH 429/567] change owner to project for azure host Signed-off-by: vinisdl --- .changeset/wise-wasps-look.md | 5 +++ .../RepoUrlPicker/AzureRepoPicker.test.tsx | 10 ++--- .../fields/RepoUrlPicker/AzureRepoPicker.tsx | 32 ++++++++-------- .../RepoUrlPicker/RepoUrlPicker.test.tsx | 37 ++++++++++++++++++- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 2 +- 5 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 .changeset/wise-wasps-look.md diff --git a/.changeset/wise-wasps-look.md b/.changeset/wise-wasps-look.md new file mode 100644 index 0000000000..34f52b3a7b --- /dev/null +++ b/.changeset/wise-wasps-look.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Change owner to project for azure host diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx index eb77240d33..0380ac6ce8 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.test.tsx @@ -44,18 +44,18 @@ describe('AzureRepoPicker', () => { }); }); - describe('owner field', () => { - it('calls onChange when the owner changes', () => { + describe('project field', () => { + it('calls onChange when the project changes', () => { const onChange = jest.fn(); const { getAllByRole } = render( , ); - const ownerInput = getAllByRole('textbox')[1]; + const projectInput = getAllByRole('textbox')[1]; - fireEvent.change(ownerInput, { target: { value: 'owner' } }); + fireEvent.change(projectInput, { target: { value: 'project' } }); - expect(onChange).toHaveBeenCalledWith({ owner: 'owner' }); + expect(onChange).toHaveBeenCalledWith({ project: 'project' }); }); }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx index 83f013c8b4..833a0849fb 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx @@ -24,14 +24,14 @@ import { Select, SelectItem } from '@backstage/core-components'; export const AzureRepoPicker = (props: { allowedOrganizations?: string[]; - allowedOwners?: string[]; + allowedProject?: string[]; rawErrors: string[]; state: RepoUrlPickerState; onChange: (state: RepoUrlPickerState) => void; }) => { const { allowedOrganizations = [], - allowedOwners = [], + allowedProject = [], rawErrors, state, onChange, @@ -41,11 +41,11 @@ export const AzureRepoPicker = (props: { ? allowedOrganizations.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; - const ownerItems: SelectItem[] = allowedOwners - ? allowedOwners.map(i => ({ label: i, value: i })) + const projectItems: SelectItem[] = allowedProject + ? allowedProject.map(i => ({ label: i, value: i })) : [{ label: 'Loading...', value: 'loading' }]; - const { organization, owner } = state; + const { organization, project } = state; return ( <> @@ -82,26 +82,26 @@ export const AzureRepoPicker = (props: { 0 && !owner} + error={rawErrors?.length > 0 && !project} > - {allowedOwners?.length ? ( + {allowedProject?.length ? ( onChange({ owner: e.target.value })} - value={owner} + id="projectInput" + onChange={e => onChange({ project: e.target.value })} + value={project} /> )} diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 85a50fa7cb..1fa723c8c6 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -58,6 +58,10 @@ describe('RepoUrlPicker', () => { byHost: () => ({ type: 'github' }), }; + const mockIntegrationsApiAzure: Partial = { + byHost: () => ({ type: 'azure' }), + }; + let mockScmAuthApi: Partial; beforeEach(() => { @@ -111,7 +115,7 @@ describe('RepoUrlPicker', () => { const { getByRole } = await renderInTestApp( { ).toBeInTheDocument(); }); + it('should render properly with allowedProject', async () => { + const { getByRole } = await renderInTestApp( + + +
    , + }} + /> +
    +
    , + ); + + expect(getByRole('option', { name: 'Backstage' })).toBeInTheDocument(); + }); + it('should render properly with title and description', async () => { const { getByText } = await renderInTestApp( { {hostType === 'azure' && ( Date: Wed, 15 May 2024 17:03:20 -0400 Subject: [PATCH 430/567] Update setup-opentelemetry.md Signed-off-by: Jaeeun Lee --- docs/tutorials/setup-opentelemetry.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md index c49eef26a2..30dde79db8 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -65,13 +65,19 @@ You can now start your Backstage instance as usual, using `yarn dev`. ## Production Setup -In your `Dockerfile` add the `--require` flag which points to the `instrumentation.js` file +In your `Dockerfile`, copy `instrumentation.js` file into the root of the working directory. + +```Dockerfile +COPY --chown=${NOT_ROOT_USER}:${NOT_ROOT_USER} packages/backend/src/instrumentation.js ./ +``` + +And then add the `--require` flag that points to the file to the CMD array. ```Dockerfile // highlight-remove-next-line CMD ["node", "packages/backend", "--config", "app-config.yaml"] // highlight-add-next-line -CMD ["node", "--require", "./src/instrumentation.js", "packages/backend", "--config", "app-config.yaml"] +CMD ["node", "--require", "./instrumentation.js", "packages/backend", "--config", "app-config.yaml"] ``` If you need to disable/configure some OpenTelemetry feature there are lots of [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) which you can tweak. From f6e2d96a1cdcb3e969f44f9e32292b164e0cbd80 Mon Sep 17 00:00:00 2001 From: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> Date: Wed, 15 May 2024 23:46:54 -0400 Subject: [PATCH 431/567] chore: Fix typo in error logging Signed-off-by: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> --- .../src/manager/plugin-manager.test.ts | 24 +++++++++---------- .../src/manager/plugin-manager.ts | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index 487729687f..340991b11f 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -59,7 +59,7 @@ describe('backend-dynamic-feature-service', () => { name: string; packageManifest: ScannedPluginManifest; indexFile?: { - retativePath: string[]; + relativePath: string[]; content: string; }; expectedLogs?(location: URL): { @@ -83,7 +83,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ kind: "new", install: () => [] }', }, @@ -127,7 +127,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: `const alpha = { $$type: '@backstage/BackendFeature' }; exports["default"] = alpha;`, }, expectedLogs(location) { @@ -170,7 +170,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: `const alpha = () => { return { $$type: '@backstage/BackendFeature' } }; alpha.$$type = '@backstage/BackendFeatureFactory'; exports["default"] = alpha;`, @@ -215,7 +215,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ kind: "new", install: () => [] }', }, @@ -262,7 +262,7 @@ describe('backend-dynamic-feature-service', () => { return { errors: [ { - message: `an error occured while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, + message: `an error occurred while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, meta: { name: 'Error', message: expect.stringContaining( @@ -305,7 +305,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: '', }, expectedLogs(location) { @@ -332,7 +332,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ something: "else", unexpectedMethod() {} }', }, @@ -360,14 +360,14 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'strange text with syntax error', }, expectedLogs(location) { return { errors: [ { - message: `an error occured while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, + message: `an error occurred while loading dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, meta: { message: expect.stringContaining('Unexpected identifier'), name: 'SyntaxError', @@ -391,7 +391,7 @@ describe('backend-dynamic-feature-service', () => { main: 'dist/index.cjs.js', }, indexFile: { - retativePath: ['dist', 'index.cjs.js'], + relativePath: ['dist', 'index.cjs.js'], content: 'exports.dynamicPluginInstaller={ kind: "legacy", scaffolder: (env)=>[] }', }, @@ -458,7 +458,7 @@ describe('backend-dynamic-feature-service', () => { mockedFiles[ path.join( url.fileURLToPath(plugin.location), - ...tc.indexFile.retativePath, + ...tc.indexFile.relativePath, ) ] = tc.indexFile.content; } diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts index edc3e20f72..95b0c6592c 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -188,7 +188,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { }; } catch (error) { this.logger.error( - `an error occured while loading dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`, + `an error occurred while loading dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`, error, ); return undefined; From 9478fbb64c4b894c31629df42155356b68eefdcf Mon Sep 17 00:00:00 2001 From: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> Date: Wed, 15 May 2024 23:55:55 -0400 Subject: [PATCH 432/567] chore(catalog-model): Remove useless test The metadata field is required by schema, and this was testing if it was optional, passing only because it was misspelled. Signed-off-by: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> --- .../src/entity/policies/FieldFormatEntityPolicy.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts index f32236e977..9fe041a839 100644 --- a/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/FieldFormatEntityPolicy.test.ts @@ -65,11 +65,6 @@ describe('FieldFormatEntityPolicy', () => { await expect(policy.enforce(data)).rejects.toThrow(/kind/); }); - it('handles missing metadata gracefully', async () => { - delete data.medatata; - await expect(policy.enforce(data)).resolves.toBe(data); - }); - it('handles missing spec gracefully', async () => { delete data.spec; await expect(policy.enforce(data)).resolves.toBe(data); From d2c36c08964e920d1d1a317be66cc1cc2ed13e2b Mon Sep 17 00:00:00 2001 From: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> Date: Wed, 15 May 2024 23:56:18 -0400 Subject: [PATCH 433/567] chore: Fix minor typo in a test Signed-off-by: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> --- packages/backend-common/src/reading/FetchUrlReader.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/reading/FetchUrlReader.test.ts b/packages/backend-common/src/reading/FetchUrlReader.test.ts index 7b5e4ebc05..8a0b24002e 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.test.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.test.ts @@ -77,7 +77,7 @@ describe('FetchUrlReader', () => { worker.use( rest.get('https://backstage.io/error', (_req, res, ctx) => { - return res(ctx.status(500), ctx.body('An internal error occured')); + return res(ctx.status(500), ctx.body('An internal error occurred')); }), ); }); From cfa0afb3074687dbe736558dc28726dc486a71a4 Mon Sep 17 00:00:00 2001 From: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> Date: Wed, 15 May 2024 23:56:48 -0400 Subject: [PATCH 434/567] chore(backend-common): Fix typo in error message Signed-off-by: Adam Harvey <33203301+adamdmharvey@users.noreply.github.com> --- packages/backend-common/src/database/connectors/mysql.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index 1ad8b0b3be..2fb3545047 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -304,7 +304,7 @@ export class MysqlConnector implements Connector { const pluginDivisionMode = this.getPluginDivisionModeConfig(); if (pluginDivisionMode !== 'database') { throw new Error( - `The MySQL driver does not suppoert plugin division mode '${pluginDivisionMode}'`, + `The MySQL driver does not support plugin division mode '${pluginDivisionMode}'`, ); } From 582099e4d1754bb7f7625f19ffa1380abc7e7966 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Thu, 16 May 2024 09:56:47 +0530 Subject: [PATCH 435/567] Updated the deployment documents Signed-off-by: Aditya Kumar --- docs/deployment/docker.md | 20 ++++++++++++++------ docs/deployment/index.md | 8 ++++++-- docs/deployment/k8s.md | 12 ++++++++---- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 9ec5460a88..f16a2ad13e 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -141,8 +141,12 @@ browser at `http://localhost:7007` ## Multi-stage Build -> NOTE: The `.dockerignore` is different in this setup, read on for more -> details. +:::note Note + +The `.dockerignore` is different in this setup, read on for more +details. + +::: This section describes how to set up a multi-stage Docker build that builds the entire project within Docker. This is typically slower than a host build, but is @@ -293,10 +297,14 @@ browser at `http://localhost:7007` ## Separate Frontend -> NOTE: This is an optional step, and you will lose out on the features of the -> `@backstage/plugin-app-backend` plugin. Most notably the frontend configuration -> will no longer be injected by the backend, you will instead need to use the -> correct configuration when building the frontend bundle. +:::note Note + +This is an optional step, and you will lose out on the features of the +`@backstage/plugin-app-backend` plugin. Most notably the frontend configuration +will no longer be injected by the backend, you will instead need to use the +correct configuration when building the frontend bundle. + +::: It is sometimes desirable to serve the frontend separately from the backend, either from a separate image or for example a static file serving provider. The diff --git a/docs/deployment/index.md b/docs/deployment/index.md index da2c29ba23..0524e71db8 100644 --- a/docs/deployment/index.md +++ b/docs/deployment/index.md @@ -13,8 +13,12 @@ This documentation shows common examples that may be useful when deploying Backstage for the first time, or for those without established deployment practices. -> Note: The _easiest_ way to explore Backstage is to visit the -> [live demo site](https://demo.backstage.io). +:::note Note + +The _easiest_ way to explore Backstage is to visit the +[live demo site](https://demo.backstage.io). + +::: At Spotify, we deploy software generally by: diff --git a/docs/deployment/k8s.md b/docs/deployment/k8s.md index 00bf7464e3..46ba273e14 100644 --- a/docs/deployment/k8s.md +++ b/docs/deployment/k8s.md @@ -107,10 +107,14 @@ $ echo -n "backstage" | base64 YmFja3N0YWdl ``` -> Note: Secrets are base64-encoded, but not encrypted. Be sure to enable -> [Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/) -> for the cluster. For storing secrets in Git, consider -> [SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git). +:::note Note + +Secrets are base64-encoded, but not encrypted. Be sure to enable +[Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/) +for the cluster. For storing secrets in Git, consider +[SealedSecrets or other solutions](https://learnk8s.io/kubernetes-secrets-in-git). + +::: The secrets can now be applied to the Kubernetes cluster: From 07bcf8c1f77f53bb474b175975b17cbdcdce5add Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 16 May 2024 07:50:24 +0200 Subject: [PATCH 436/567] wip Signed-off-by: bnechyporenko --- beps/0001-notifications-system/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beps/0001-notifications-system/README.md b/beps/0001-notifications-system/README.md index 06587f9e12..86b299387c 100644 --- a/beps/0001-notifications-system/README.md +++ b/beps/0001-notifications-system/README.md @@ -148,7 +148,7 @@ The link is a relative or absolute URL. As an example, it can be used: - by an external system to request an action within an asynchronous task - by a BE plugin to provide link to other part of the Backstage UI (i.e. to the Catalog) -The metadata is a flexible JSON like field, where an additional payload can be stored. +The metadata is an opaque JSON field, where an additional payload can be stored. The format of this data is owned by the notification sender and is tied to the notification topic, i.e. notifications sent from the source on the same topic should use a compatible metadata format. The primary purpose of this field is to allow for custom processing and rendering based on the additional metadata. The additional links are an array of title-URL pairs. They can represent immediate actions on the notification (i.e. yes-no) or lead the user to additional details. From 819754e57e175de048b0d91f09286205afbe4f02 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 16 May 2024 09:18:24 +0200 Subject: [PATCH 437/567] refactor: start deprecating legacy service helpers Signed-off-by: Camila Belo --- .../implementations/cache/cacheServiceFactory.ts | 5 +++-- packages/backend-common/api-report.md | 10 +++++----- packages/backend-common/src/cache/CacheManager.ts | 2 ++ .../backend-common/src/logging/createRootLogger.ts | 5 +++++ packages/backend-common/src/logging/globalLoggers.ts | 8 ++++++++ packages/backend-tasks/api-report.md | 2 +- packages/backend-tasks/src/tasks/TaskScheduler.ts | 4 ++++ 7 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts index b91356a13a..cb4e04d574 100644 --- a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts @@ -25,10 +25,11 @@ export const cacheServiceFactory = createServiceFactory({ service: coreServices.cache, deps: { config: coreServices.rootConfig, + logger: coreServices.rootLogger, plugin: coreServices.pluginMetadata, }, - async createRootContext({ config }) { - return CacheManager.fromConfig(config); + async createRootContext({ config, logger }) { + return CacheManager.fromConfig(config, { logger }); }, async factory({ plugin }, manager) { return manager.forPlugin(plugin.getId()).getClient(); diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 42df32dc1d..650f50a159 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -218,7 +218,7 @@ export function cacheToPluginCacheManager( cache: CacheClient, ): PluginCacheManager; -// @public +// @public @deprecated export const coloredFormat: winston.Logform.Format; // @public @@ -259,7 +259,7 @@ export function createLegacyAuthAdapters< : {}), >(options: TOptions): TAdapters; -// @public +// @public @deprecated export function createRootLogger( options?: winston.LoggerOptions, env?: NodeJS.ProcessEnv, @@ -366,10 +366,10 @@ export class GerritUrlReader implements UrlReader { toString(): string; } -// @public +// @public @deprecated export function getRootLogger(): winston.Logger; -// @public +// @public @deprecated export function getVoidLogger(): winston.Logger; // @public @deprecated @@ -815,7 +815,7 @@ export type ServiceBuilder = { start(): Promise; }; -// @public +// @public @deprecated export function setRootLogger(newLogger: winston.Logger): void; // @public @deprecated diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index ec8a1d93f7..8af742be8c 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -72,6 +72,8 @@ export class CacheManager { config.getOptionalString('backend.cache.connection') || ''; const useRedisSets = config.getOptionalBoolean('backend.cache.useRedisSets') ?? true; + + // TODO: Make logger required and remove the default logger after moving this class to the `backstage-defaults`package const logger = (options.logger || getRootLogger()).child({ type: 'cacheManager', }); diff --git a/packages/backend-common/src/logging/createRootLogger.ts b/packages/backend-common/src/logging/createRootLogger.ts index f9a3ed4ba0..16554750ef 100644 --- a/packages/backend-common/src/logging/createRootLogger.ts +++ b/packages/backend-common/src/logging/createRootLogger.ts @@ -59,6 +59,8 @@ const colorizer = format.colorize(); * Creates a pretty printed winston log formatter. * * @public + * @deprecated As we are going to deprecate the legacy backend, this formatter utility will be removed in the future. + * If you need to format logs in the new system, please use the `WinstonLogger.colorFormat()` from `@backstage/backend-app-api` instead. */ export const coloredFormat = format.combine( format.timestamp(), @@ -96,6 +98,9 @@ export const coloredFormat = format.combine( * instances passed to plugins etc, in a given backend. * * @public + * @deprecated As we are going to deprecate the legacy backend, this function will be removed in the future. + * If you need to create the root logger in the new system, please check out this documentation: + * https://backstage.io/docs/backend-system/core-services/logger */ export function createRootLogger( options: winston.LoggerOptions = {}, diff --git a/packages/backend-common/src/logging/globalLoggers.ts b/packages/backend-common/src/logging/globalLoggers.ts index 8b473db211..cbd5279d29 100644 --- a/packages/backend-common/src/logging/globalLoggers.ts +++ b/packages/backend-common/src/logging/globalLoggers.ts @@ -21,6 +21,8 @@ import { createRootLogger } from './createRootLogger'; * A logger that just throws away all messages. * * @public + * @deprecated As we are going to deprecate the legacy backend, this function will be removed in the future. + * If you need to mock the root logger in the new system, please use `mockServices.logger.mock()` from `@backstage/test-utils` instead. */ export function getVoidLogger(): winston.Logger { return winston.createLogger({ @@ -34,6 +36,9 @@ let rootLogger: winston.Logger; * Gets the current root logger. * * @public + * @deprecated As we are going to deprecate the legacy backend, this function will be removed in the future. + * If you need to get the root logger in the new system, please check out this documentation: + * https://backstage.io/docs/backend-system/core-services/logger */ export function getRootLogger(): winston.Logger { if (!rootLogger) { @@ -55,6 +60,9 @@ export function getRootLogger(): winston.Logger { * behavior, you would instead call {@link createRootLogger}. * * @public + * @deprecated As we are going to deprecate the legacy backend, this function will be removed in the future. + * If you need to set the root logger in the new system, please check out this documentation: + * https://backstage.io/docs/backend-system/core-services/logger */ export function setRootLogger(newLogger: winston.Logger) { rootLogger = newLogger; diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index 00ef28c9ce..4bfda20c36 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -94,7 +94,7 @@ export class TaskScheduler { databaseManager: PluginDatabaseManager; logger: LoggerService; }): PluginTaskScheduler; - // (undocumented) + // @deprecated (undocumented) static fromConfig( config: Config, options?: { diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts index 672c52cc10..cc589ea949 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts @@ -35,6 +35,10 @@ import { LoggerService } from '@backstage/backend-plugin-api'; * @public */ export class TaskScheduler { + /** + * @deprecated + * It is only used by the legacy backend system, and should not be used in the new backend system. + */ static fromConfig( config: Config, options?: { From eb34b87d5a8d8ec1f103d53c0f65e1abdf53e9a0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 15 May 2024 09:09:54 +0200 Subject: [PATCH 438/567] refactor: stop using getVoidLogger in tests Signed-off-by: Camila Belo --- .../rootLifecycleServiceFactory.test.ts | 10 +-- .../reading/AwsCodeCommitUrlReader.test.ts | 4 +- .../src/reading/AwsS3UrlReader.test.ts | 4 +- .../src/reading/AzureUrlReader.test.ts | 4 +- .../src/reading/BitbucketUrlReader.test.ts | 4 +- .../src/reading/FetchUrlReader.test.ts | 10 ++- .../src/reading/GerritUrlReader.test.ts | 4 +- .../src/reading/GiteaUrlReader.test.ts | 8 +- .../src/reading/GitlabUrlReader.test.ts | 4 +- .../src/reading/GoogleGcsUrlReader.test.ts | 4 +- .../src/reading/HarnessUrlReader.test.ts | 8 +- .../src/tokens/ServerTokenManager.test.ts | 4 +- .../src/tasks/LocalTaskWorker.test.ts | 4 +- .../src/tasks/PluginTaskSchedulerImpl.test.ts | 13 ++-- .../tasks/PluginTaskSchedulerJanitor.test.ts | 5 +- .../src/tasks/TaskScheduler.test.ts | 10 ++- .../src/tasks/TaskWorker.test.ts | 5 +- .../src/next/services/mockServices.ts | 22 ++++-- .../src/lib/assets/StaticAssetsStore.test.ts | 5 +- plugins/app-backend/src/lib/config.test.ts | 8 +- .../app-backend/src/service/router.test.ts | 10 +-- .../src/identity/KeyStores.test.ts | 8 +- .../src/identity/StaticTokenIssuer.test.ts | 4 +- .../src/identity/TokenFactory.test.ts | 4 +- .../CatalogAuthResolverContext.test.ts | 3 +- .../src/providers/oidc/provider.test.ts | 8 +- .../AwsS3DiscoveryProcessor.test.ts | 5 +- .../src/providers/AwsS3EntityProvider.test.ts | 6 +- .../AzureDevOpsDiscoveryProcessor.test.ts | 8 +- .../AzureDevOpsEntityProvider.test.ts | 4 +- .../BitbucketCloudEntityProvider.test.ts | 11 ++- .../providers/BitbucketCloudEntityProvider.ts | 1 + .../BitbucketServerEntityProvider.test.ts | 12 ++- .../providers/GerritEntityProvider.test.ts | 10 ++- .../GithubDiscoveryProcessor.test.ts | 8 +- .../GithubOrgReaderProcessor.test.ts | 4 +- .../providers/GithubEntityProvider.test.ts | 6 +- .../GithubMultiOrgEntityProvider.test.ts | 6 +- .../providers/GithubOrgEntityProvider.test.ts | 18 ++--- .../src/GitLabDiscoveryProcessor.test.ts | 8 +- .../src/lib/client.test.ts | 74 ++++++++++--------- .../GitlabDiscoveryEntityProvider.test.ts | 10 ++- .../GitlabOrgDiscoveryEntityProvider.test.ts | 10 ++- .../src/module/WrapperProviders.test.ts | 7 +- .../src/microsoftGraph/read.test.ts | 26 +++---- .../MicrosoftGraphOrgEntityProvider.test.ts | 4 +- .../MicrosoftGraphOrgReaderProcessor.test.ts | 4 +- .../src/OpenApiRefProcessor.test.ts | 5 +- .../providers/PuppetDbEntityProvider.test.ts | 4 +- .../stitcher/performStitching.test.ts | 5 +- .../codeowners/CodeOwnersProcessor.test.ts | 4 +- .../modules/core/UrlReaderProcessor.test.ts | 17 ++--- .../DefaultCatalogProcessingEngine.test.ts | 18 ++--- ...faultCatalogProcessingOrchestrator.test.ts | 12 +-- .../service/DefaultEntitiesCatalog.test.ts | 52 ++++++------- .../src/service/createRouter.test.ts | 7 +- .../src/stitching/DefaultStitcher.test.ts | 5 +- .../src/tests/integration.test.ts | 4 +- .../src/service/router.test.ts | 3 +- .../AwsSqsConsumingEventPublisher.test.ts | 8 +- .../src/service/DefaultEventBroker.test.ts | 4 +- .../src/service/EventsBackend.test.ts | 7 +- .../HttpPostIngressEventPublisher.test.ts | 4 +- plugins/events-node/package.json | 1 + .../src/api/DefaultEventsService.test.ts | 4 +- .../example-todo-list-backend/package.json | 1 + .../src/service/router.test.ts | 4 +- .../src/auth/AzureIdentityStrategy.test.ts | 4 +- .../src/cluster-locator/index.test.ts | 7 +- .../service/KubernetesFanOutHandler.test.ts | 10 ++- .../src/service/KubernetesFetcher.test.ts | 6 +- .../src/service/KubernetesProxy.test.ts | 9 ++- .../src/auth/PinnipedHelper.test.ts | 4 +- .../src/service/router.test.ts | 3 +- .../src/service/router.test.ts | 3 +- .../src/ServerPermissionClient.test.ts | 3 +- .../src/service/router.config.test.ts | 8 +- .../proxy-backend/src/service/router.test.ts | 13 ++-- .../confluenceToMarkdown.examples.test.ts | 6 +- .../confluence/confluenceToMarkdown.test.ts | 6 +- .../actions/githubRepoPush.examples.test.ts | 3 +- .../src/actions/githubRepoPush.test.ts | 3 +- .../tasks/NunjucksWorkflowRunner.test.ts | 9 ++- .../tasks/StorageTaskBroker.test.ts | 8 +- .../src/scaffolder/tasks/TaskWorker.test.ts | 12 ++- .../src/service/router.test.ts | 10 +-- .../src/actions/mockActionConext.ts | 5 +- .../src/actions/gitHelpers.test.ts | 24 +++--- .../package.json | 1 + .../engines/ElasticSearchSearchEngine.test.ts | 10 +-- .../ElasticSearchSearchEngineIndexer.test.ts | 8 +- .../ToolDocumentCollatorFactory.test.ts | 8 +- ...ckOverflowQuestionsCollatorFactory.test.ts | 8 +- .../DefaultTechDocsCollatorFactory.test.ts | 10 ++- plugins/search-backend-node/package.json | 1 + .../src/IndexBuilder.test.ts | 4 +- .../search-backend-node/src/Scheduler.test.ts | 4 +- ...ewlineDelimitedJsonCollatorFactory.test.ts | 4 +- .../src/engines/LunrSearchEngine.test.ts | 30 ++++---- .../search-backend/src/service/router.test.ts | 9 +-- plugins/signals-backend/package.json | 1 + .../src/service/SignalManager.test.ts | 4 +- .../src/service/router.test.ts | 8 +- .../src/cache/TechDocsCache.test.ts | 7 +- .../src/cache/cacheMiddleware.test.ts | 5 +- .../search/DefaultTechDocsCollator.test.ts | 9 ++- .../src/service/DocsSynchronizer.test.ts | 7 +- .../src/service/router.test.ts | 7 +- .../src/stages/generate/generators.test.ts | 8 +- .../src/stages/generate/helpers.test.ts | 9 ++- .../src/stages/prepare/dir.test.ts | 5 +- .../src/stages/publish/awsS3.test.ts | 9 ++- .../stages/publish/azureBlobStorage.test.ts | 9 ++- .../src/stages/publish/googleStorage.test.ts | 9 ++- .../src/stages/publish/local.test.ts | 9 ++- .../src/stages/publish/openStackSwift.test.ts | 9 ++- .../src/stages/publish/publish.test.ts | 5 +- yarn.lock | 5 ++ 118 files changed, 533 insertions(+), 433 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts index 91cb9031ae..5992e38d50 100644 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { BackendLifecycleImpl } from './rootLifecycleServiceFactory'; +import { mockServices } from '@backstage/backend-test-utils'; describe('lifecycleService', () => { it('should execute registered shutdown hook', async () => { - const service = new BackendLifecycleImpl(getVoidLogger()); + const service = new BackendLifecycleImpl(mockServices.logger.mock()); const hook = jest.fn(); service.addShutdownHook(() => hook()); // should not execute the hook more than once. @@ -30,7 +30,7 @@ describe('lifecycleService', () => { }); it('should not throw errors', async () => { - const service = new BackendLifecycleImpl(getVoidLogger()); + const service = new BackendLifecycleImpl(mockServices.logger.mock()); service.addShutdownHook(() => { throw new Error('oh no'); }); @@ -38,7 +38,7 @@ describe('lifecycleService', () => { }); it('should not throw async errors', async () => { - const service = new BackendLifecycleImpl(getVoidLogger()); + const service = new BackendLifecycleImpl(mockServices.logger.mock()); service.addShutdownHook(async () => { throw new Error('oh no'); }); @@ -46,7 +46,7 @@ describe('lifecycleService', () => { }); it('should reject hooks after trigger', async () => { - const service = new BackendLifecycleImpl(getVoidLogger()); + const service = new BackendLifecycleImpl(mockServices.logger.mock()); await service.startup(); expect(() => { service.addStartupHook(() => {}); diff --git a/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts b/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts index 44d785d9c4..6263d073fc 100644 --- a/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts +++ b/packages/backend-common/src/reading/AwsCodeCommitUrlReader.test.ts @@ -16,7 +16,6 @@ import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; -import { getVoidLogger } from '../logging'; import { DefaultReadTreeResponseFactory } from './tree'; import { AwsCodeCommitUrlReader, parseUrl } from './AwsCodeCommitUrlReader'; import { UrlReaderPredicateTuple } from './types'; @@ -34,6 +33,7 @@ import { AwsCodeCommitIntegration, readAwsCodeCommitIntegrationConfig, } from '@backstage/integration'; +import { mockServices } from '@backstage/backend-test-utils'; const AMAZON_AWS_CODECOMMIT_HOST = 'console.aws.amazon.com'; @@ -202,7 +202,7 @@ describe('AwsCodeCommitUrlReader', () => { const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { return AwsCodeCommitUrlReader.factory({ config: new ConfigReader(config), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory, }); }; diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts index e3247f190b..15f46199da 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts @@ -16,7 +16,6 @@ import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; -import { getVoidLogger } from '../logging'; import { DefaultReadTreeResponseFactory } from './tree'; import { DEFAULT_REGION, AwsS3UrlReader, parseUrl } from './AwsS3UrlReader'; import { @@ -37,6 +36,7 @@ import { } from '@aws-sdk/client-s3'; import { sdkStreamMixin } from '@aws-sdk/util-stream-node'; import fs from 'fs'; +import { mockServices } from '@backstage/backend-test-utils'; const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), @@ -166,7 +166,7 @@ describe('AwsS3UrlReader', () => { const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { return AwsS3UrlReader.factory({ config: new ConfigReader(config), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory, }); }; diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index 2b4bc84be7..ce2385507b 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -25,6 +25,7 @@ import { } from '@backstage/integration'; import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; @@ -32,7 +33,6 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import path from 'path'; import { NotModifiedError } from '@backstage/errors'; -import { getVoidLogger } from '../logging'; import { AzureUrlReader } from './AzureUrlReader'; import { DefaultReadTreeResponseFactory } from './tree'; @@ -42,7 +42,7 @@ type AzureIntegrationConfigLike = Partial< credentials?: Partial[]; }; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const mockDir = createMockDirectory({ mockOsTmpDir: true }); diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts index 4258ac9d5b..edd8d6d3fc 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts @@ -21,6 +21,7 @@ import { } from '@backstage/integration'; import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; @@ -30,10 +31,9 @@ import path from 'path'; import { NotModifiedError } from '@backstage/errors'; import { BitbucketUrlReader } from './BitbucketUrlReader'; import { DefaultReadTreeResponseFactory } from './tree'; -import { getVoidLogger } from '../logging'; import getRawBody from 'raw-body'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('BitbucketUrlReader.factory', () => { it('only apply integration configs not inherited from bitbucketCloud or bitbucketServer', () => { diff --git a/packages/backend-common/src/reading/FetchUrlReader.test.ts b/packages/backend-common/src/reading/FetchUrlReader.test.ts index 7b5e4ebc05..ce66e4e215 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.test.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.test.ts @@ -16,10 +16,12 @@ import { ConfigReader } from '@backstage/config'; import { NotFoundError, NotModifiedError } from '@backstage/errors'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { getVoidLogger } from '../logging'; import { FetchUrlReader } from './FetchUrlReader'; import { DefaultReadTreeResponseFactory } from './tree'; @@ -105,7 +107,7 @@ describe('FetchUrlReader', () => { }, }, }), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory: DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), }), @@ -155,7 +157,7 @@ describe('FetchUrlReader', () => { }, }, }), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory: DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), }), diff --git a/packages/backend-common/src/reading/GerritUrlReader.test.ts b/packages/backend-common/src/reading/GerritUrlReader.test.ts index 14f96b529a..ca4a2707f3 100644 --- a/packages/backend-common/src/reading/GerritUrlReader.test.ts +++ b/packages/backend-common/src/reading/GerritUrlReader.test.ts @@ -16,6 +16,7 @@ import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; @@ -29,7 +30,6 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import fs from 'fs-extra'; import path from 'path'; -import { getVoidLogger } from '../logging'; import { UrlReaderPredicateTuple } from './types'; import { DefaultReadTreeResponseFactory } from './tree'; import { @@ -86,7 +86,7 @@ const gerritProcessorWithGitiles = new GerritUrlReader( const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { return GerritUrlReader.factory({ config: new ConfigReader(config), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory, }); }; diff --git a/packages/backend-common/src/reading/GiteaUrlReader.test.ts b/packages/backend-common/src/reading/GiteaUrlReader.test.ts index f8658a2241..0b5983eefd 100644 --- a/packages/backend-common/src/reading/GiteaUrlReader.test.ts +++ b/packages/backend-common/src/reading/GiteaUrlReader.test.ts @@ -14,13 +14,15 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { GiteaIntegration, readGiteaConfig } from '@backstage/integration'; import { JsonObject } from '@backstage/types'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { getVoidLogger } from '../logging'; import { UrlReaderPredicateTuple } from './types'; import { DefaultReadTreeResponseFactory } from './tree'; import getRawBody from 'raw-body'; @@ -55,7 +57,7 @@ const giteaProcessor = new GiteaUrlReader( const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { return GiteaUrlReader.factory({ config: new ConfigReader(config), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory, }); }; diff --git a/packages/backend-common/src/reading/GitlabUrlReader.test.ts b/packages/backend-common/src/reading/GitlabUrlReader.test.ts index 3c065e8b81..46276fcdda 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.test.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.test.ts @@ -17,13 +17,13 @@ import { ConfigReader } from '@backstage/config'; import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import path from 'path'; -import { getVoidLogger } from '../logging'; import { GitlabUrlReader } from './GitlabUrlReader'; import { DefaultReadTreeResponseFactory } from './tree'; import { NotModifiedError, NotFoundError } from '@backstage/errors'; @@ -32,7 +32,7 @@ import { readGitLabIntegrationConfig, } from '@backstage/integration'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const mockDir = createMockDirectory({ mockOsTmpDir: true }); diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts index 25bce25686..bc31818d23 100644 --- a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts +++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts @@ -16,11 +16,11 @@ import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; -import { getVoidLogger } from '../logging'; import { DefaultReadTreeResponseFactory } from './tree'; import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; import { UrlReaderPredicateTuple } from './types'; import packageinfo from '../../package.json'; +import { mockServices } from '@backstage/backend-test-utils'; const bucketGetFilesMock = jest.fn(); jest.mock('@google-cloud/storage', () => { @@ -44,7 +44,7 @@ describe('GcsUrlReader', () => { const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { return GoogleGcsUrlReader.factory({ config: new ConfigReader(config), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory: DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), }), diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index 2741b75fd5..7cc37add3b 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -14,13 +14,15 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { HarnessIntegration, readHarnessConfig } from '@backstage/integration'; import { JsonObject } from '@backstage/types'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { getVoidLogger } from '../logging'; import { UrlReaderPredicateTuple } from './types'; import { DefaultReadTreeResponseFactory } from './tree'; import getRawBody from 'raw-body'; @@ -52,7 +54,7 @@ const harnessProcessor = new HarnessUrlReader( const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { return HarnessUrlReader.factory({ config: new ConfigReader(config), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), treeResponseFactory, }); }; diff --git a/packages/backend-common/src/tokens/ServerTokenManager.test.ts b/packages/backend-common/src/tokens/ServerTokenManager.test.ts index 8870b4ed1c..2f99164ec1 100644 --- a/packages/backend-common/src/tokens/ServerTokenManager.test.ts +++ b/packages/backend-common/src/tokens/ServerTokenManager.test.ts @@ -16,17 +16,17 @@ import { ConfigReader } from '@backstage/config'; import * as jose from 'jose'; -import { getVoidLogger } from '../logging'; import { ServerTokenManager } from './ServerTokenManager'; import { TokenManager } from './types'; import { DateTime } from 'luxon'; +import { mockServices } from '@backstage/backend-test-utils'; const emptyConfig = new ConfigReader({}); const configWithSecret = new ConfigReader({ backend: { auth: { keys: [{ secret: 'a-secret-key' }] } }, }); const env = process.env; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('ServerTokenManager', () => { beforeEach(() => { diff --git a/packages/backend-tasks/src/tasks/LocalTaskWorker.test.ts b/packages/backend-tasks/src/tasks/LocalTaskWorker.test.ts index 4fd4949478..d4d3d16067 100644 --- a/packages/backend-tasks/src/tasks/LocalTaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/LocalTaskWorker.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { LocalTaskWorker } from './LocalTaskWorker'; +import { mockServices } from '@backstage/backend-test-utils'; describe('LocalTaskWorker', () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); it('runs the happy path (with iso duration) and handles cancellation', async () => { const fn = jest.fn(); diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts index 4b40791cb5..77965632e9 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { + TestDatabaseId, + TestDatabases, + mockServices, +} from '@backstage/backend-test-utils'; import { ConflictError, NotFoundError } from '@backstage/errors'; import { Duration } from 'luxon'; import { migrateBackendTasks } from '../database/migrateBackendTasks'; @@ -51,10 +54,8 @@ describe('PluginTaskManagerImpl', () => { async function init(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); await migrateBackendTasks(knex); - const manager = new PluginTaskSchedulerImpl( - async () => knex, - getVoidLogger(), - ); + const logger = mockServices.logger.mock(); + const manager = new PluginTaskSchedulerImpl(async () => knex, logger); return { knex, manager }; } diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts index 1f0363625e..93b5d7d189 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { TestDatabases } from '@backstage/backend-test-utils'; +import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { Knex } from 'knex'; import { Duration } from 'luxon'; import waitForExpect from 'wait-for-expect'; @@ -36,7 +35,7 @@ const getTask = async (knex: Knex): Promise => { }; describe('PluginTaskSchedulerJanitor', () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const databases = TestDatabases.create({ ids: [ /* 'MYSQL_8' not supported yet */ diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.test.ts b/packages/backend-tasks/src/tasks/TaskScheduler.test.ts index d286eaee38..24ce73712f 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.test.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.test.ts @@ -14,8 +14,12 @@ * limitations under the License. */ -import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; -import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { DatabaseManager } from '@backstage/backend-common'; +import { + TestDatabaseId, + TestDatabases, + mockServices, +} from '@backstage/backend-test-utils'; import { Duration } from 'luxon'; import waitForExpect from 'wait-for-expect'; import { TaskScheduler } from './TaskScheduler'; @@ -24,7 +28,7 @@ import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; jest.setTimeout(60_000); describe('TaskScheduler', () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const databases = TestDatabases.create(); const testScopedSignal = createTestScopedSignal(); diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index a2ecdaa779..10ebfde5b4 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { TestDatabases } from '@backstage/backend-test-utils'; +import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { Duration, DateTime } from 'luxon'; import waitForExpect from 'wait-for-expect'; import { migrateBackendTasks } from '../database/migrateBackendTasks'; @@ -27,7 +26,7 @@ import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; jest.setTimeout(60_000); describe('TaskWorker', () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const databases = TestDatabases.create(); const testScopedSignal = createTestScopedSignal(); diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index 05b3ae927c..a486a9ab0c 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -57,6 +57,17 @@ import { eventsServiceRef, } from '@backstage/plugin-events-node'; +/** @internal */ +function createLoggerMock() { + return { + child: jest.fn().mockImplementation(() => createLoggerMock()), + debug: jest.fn(), + error: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + }; +} + /** @internal */ function simpleFactory< TService, @@ -358,13 +369,10 @@ export namespace mockServices { export namespace logger { export const factory = loggerServiceFactory; - export const mock = simpleMock(coreServices.logger, () => ({ - child: jest.fn(), - debug: jest.fn(), - error: jest.fn(), - info: jest.fn(), - warn: jest.fn(), - })); + + export const mock = simpleMock(coreServices.logger, () => + createLoggerMock(), + ); } export namespace permissions { diff --git a/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts b/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts index 41408396fe..51b949a623 100644 --- a/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts +++ b/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts @@ -15,11 +15,10 @@ */ import { Knex as KnexType } from 'knex'; -import { getVoidLogger } from '@backstage/backend-common'; -import { TestDatabases } from '@backstage/backend-test-utils'; +import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { StaticAssetsStore } from './StaticAssetsStore'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); function createDatabaseManager( client: KnexType, diff --git a/plugins/app-backend/src/lib/config.test.ts b/plugins/app-backend/src/lib/config.test.ts index 9a6e39da4e..34f744d5b8 100644 --- a/plugins/app-backend/src/lib/config.test.ts +++ b/plugins/app-backend/src/lib/config.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; import { injectConfig } from './config'; describe('injectConfig', () => { @@ -24,7 +26,7 @@ describe('injectConfig', () => { const baseOptions = { appConfigs: [], staticDir: mockDir.path, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }; beforeEach(() => { diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index 6fffdb149f..b7ad65706c 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { AppConfig, ConfigReader } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; @@ -22,6 +21,7 @@ import { resolve as resolvePath } from 'path'; import request from 'supertest'; import { createRouter } from './router'; import { loadConfigSchema } from '@backstage/config-loader'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib/config', () => ({ injectConfig: jest.fn(), @@ -38,7 +38,7 @@ describe('createRouter', () => { beforeAll(async () => { const router = await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({}), appPackageName: 'example-app', }); @@ -96,7 +96,7 @@ describe('createRouter with static fallback handler', () => { }); const router = await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({}), appPackageName: 'example-app', staticFallbackHandler, @@ -129,7 +129,7 @@ describe('createRouter config schema test', () => { it('uses an external schema', async () => { await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({ test: 'value', }), @@ -170,7 +170,7 @@ describe('createRouter config schema test', () => { it('uses no external schema', async () => { await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({ test: 'value', }), diff --git a/plugins/auth-backend/src/identity/KeyStores.test.ts b/plugins/auth-backend/src/identity/KeyStores.test.ts index 6c034eb64b..0d4ffac4d8 100644 --- a/plugins/auth-backend/src/identity/KeyStores.test.ts +++ b/plugins/auth-backend/src/identity/KeyStores.test.ts @@ -20,7 +20,7 @@ import { DatabaseKeyStore } from './DatabaseKeyStore'; import { FirestoreKeyStore } from './FirestoreKeyStore'; import { KeyStores } from './KeyStores'; import { MemoryKeyStore } from './MemoryKeyStore'; -import { getVoidLogger } from '@backstage/backend-common'; +import { mockServices } from '@backstage/backend-test-utils'; describe('KeyStores', () => { const defaultConfigOptions = { @@ -35,7 +35,7 @@ describe('KeyStores', () => { it('reads auth section from config', async () => { const configSpy = jest.spyOn(defaultConfig, 'getOptionalConfig'); const keyStore = await KeyStores.fromConfig(defaultConfig, { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), database: AuthDatabase.forTesting(), }); @@ -50,7 +50,7 @@ describe('KeyStores', () => { it('can handle without auth config', async () => { const keyStore = await KeyStores.fromConfig(new ConfigReader({}), { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), database: AuthDatabase.forTesting(), }); expect(keyStore).toBeInstanceOf(DatabaseKeyStore); @@ -78,7 +78,7 @@ describe('KeyStores', () => { }; const config = new ConfigReader(configOptions); const keyStore = await KeyStores.fromConfig(config, { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), database: AuthDatabase.forTesting(), }); diff --git a/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts b/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts index 3e5d659d54..92b5888113 100644 --- a/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts +++ b/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { StaticTokenIssuer } from './StaticTokenIssuer'; import { createLocalJWKSet, jwtVerify } from 'jose'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { StaticKeyStore } from './StaticKeyStore'; +import { mockServices } from '@backstage/backend-test-utils'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const entityRef = stringifyEntityRef({ kind: 'User', namespace: 'default', diff --git a/plugins/auth-backend/src/identity/TokenFactory.test.ts b/plugins/auth-backend/src/identity/TokenFactory.test.ts index 0994e44f68..5ee0ea83c0 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.test.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { base64url, @@ -25,8 +24,9 @@ import { import { MemoryKeyStore } from './MemoryKeyStore'; import { TokenFactory } from './TokenFactory'; import { tokenTypes } from '@backstage/plugin-auth-node'; +import { mockServices } from '@backstage/backend-test-utils'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); function jwtKid(jwt: string): string { const header = decodeProtectedHeader(jwt); diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts index d16ba0303a..a69c9c90d4 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.test.ts @@ -15,7 +15,6 @@ */ import { CatalogAuthResolverContext } from './CatalogAuthResolverContext'; -import { getVoidLogger } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { mockServices } from '@backstage/backend-test-utils'; import { TokenIssuer } from '../../identity/types'; @@ -32,7 +31,7 @@ describe('CatalogAuthResolverContext', () => { it('adds kind to filter when missing', async () => { const context = CatalogAuthResolverContext.create({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), catalogApi: mockCatalogApi as CatalogApi, tokenIssuer: {} as TokenIssuer, tokenManager: mockServices.tokenManager(), diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index f2172a796d..c7e9c68580 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { getVoidLogger } from '@backstage/backend-common'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { @@ -112,7 +114,7 @@ describe('oidc.create', () => { clientSecret: 'clientSecret', }, }), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), resolverContext: { issueToken: jest.fn(), findCatalogUser: jest.fn(), diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts index 7818b21bd1..804a17f4f0 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getVoidLogger, UrlReaders } from '@backstage/backend-common'; +import { UrlReaders } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { AwsS3DiscoveryProcessor } from './AwsS3DiscoveryProcessor'; import { @@ -33,6 +33,7 @@ import { sdkStreamMixin } from '@aws-sdk/util-stream-node'; import fs from 'fs'; import path from 'path'; import YAML from 'yaml'; +import { mockServices } from '@backstage/backend-test-utils'; const s3Client = mockClient(S3Client); const object: Object = { @@ -43,7 +44,7 @@ const output: ListObjectsV2Output = { Contents: objectList, }; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const reader = UrlReaders.default({ logger, config: new ConfigReader({ diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts index e9e17ad76a..5367c17742 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, @@ -26,6 +25,7 @@ import { AwsS3EntityProvider } from './AwsS3EntityProvider'; import { mockClient } from 'aws-sdk-client-mock'; import 'aws-sdk-client-mock-jest'; import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'; +import { mockServices } from '@backstage/backend-test-utils'; class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; @@ -40,7 +40,7 @@ class PersistingTaskRunner implements TaskRunner { } } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('AwsS3EntityProvider', () => { const createObjectList = (keys: string[]) => { @@ -79,7 +79,7 @@ describe('AwsS3EntityProvider', () => { }); afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); process.env.AWS_REGION = undefined; }); diff --git a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts index 6df555f190..e692be66e3 100644 --- a/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-azure/src/processors/AzureDevOpsDiscoveryProcessor.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { @@ -22,6 +21,7 @@ import { parseUrl, } from './AzureDevOpsDiscoveryProcessor'; import { codeSearch } from '../lib'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib'); const mockCodeSearch = codeSearch as jest.MockedFunction; @@ -106,7 +106,7 @@ describe('AzureDevOpsDiscoveryProcessor', () => { azure: [{ host: 'dev.azure.com', token: 'blob' }], }, }), - { logger: getVoidLogger() }, + { logger: mockServices.logger.mock() }, ); const location: LocationSpec = { type: 'not-azure-discovery', @@ -128,7 +128,7 @@ describe('AzureDevOpsDiscoveryProcessor', () => { ], }, }), - { logger: getVoidLogger() }, + { logger: mockServices.logger.mock() }, ); const location: LocationSpec = { type: 'azure-discovery', @@ -148,7 +148,7 @@ describe('AzureDevOpsDiscoveryProcessor', () => { github: [{ host: 'dev.azure.com', token: 'blob' }], }, }), - { logger: getVoidLogger() }, + { logger: mockServices.logger.mock() }, ); beforeEach(() => { diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts index d89f10c949..b7ada509a7 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, @@ -25,6 +24,7 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { CodeSearchResultItem } from '../lib'; import { AzureDevOpsEntityProvider } from './AzureDevOpsEntityProvider'; import { codeSearch } from '../lib'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib'); const mockCodeSearch = codeSearch as jest.MockedFunction; @@ -42,7 +42,7 @@ class PersistingTaskRunner implements TaskRunner { } } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('AzureDevOpsEntityProvider', () => { afterEach(() => { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts index 6a21d95afa..55b701e016 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts @@ -14,13 +14,16 @@ * limitations under the License. */ -import { getVoidLogger, TokenManager } from '@backstage/backend-common'; +import { TokenManager } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, TaskRunner, } from '@backstage/backend-tasks'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity, LocationEntity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; @@ -54,7 +57,7 @@ class PersistingTaskRunner implements TaskRunner { } } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const server = setupServer(); @@ -152,7 +155,7 @@ describe('BitbucketCloudEntityProvider', () => { }; afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); schedule.reset(); }); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts index 9135e0717f..b52d28a52e 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts @@ -146,6 +146,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { return schedule.run({ id: taskId, fn: async () => { + console.log('---> child', this.logger); const logger = this.logger.child({ class: BitbucketCloudEntityProvider.prototype.constructor.name, taskId, diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts index 3eff56e6f5..9624e396cd 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts @@ -14,13 +14,15 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, TaskRunner, } from '@backstage/backend-tasks'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { rest } from 'msw'; @@ -53,7 +55,7 @@ function pagedResponse(values: any): BitbucketServerPagedResponse { } as BitbucketServerPagedResponse; } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const server = setupServer(); @@ -102,7 +104,9 @@ function setupStubs(projects: Project[], baseUrl: string) { describe('BitbucketServerEntityProvider', () => { setupRequestMockHandlers(server); - afterEach(() => jest.resetAllMocks()); + afterEach(() => { + jest.clearAllMocks(); + }); it('no provider config', () => { const schedule = new PersistingTaskRunner(); diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts index 9892b60a21..110a927655 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts @@ -14,13 +14,15 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, TaskRunner, } from '@backstage/backend-tasks'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import fs from 'fs-extra'; @@ -52,13 +54,13 @@ class PersistingTaskRunner implements TaskRunner { } } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('GerritEntityProvider', () => { setupRequestMockHandlers(server); afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); const config = new ConfigReader({ diff --git a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts index 7fbee87161..afaaa573c3 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { DefaultGithubCredentialsProvider, @@ -23,6 +22,7 @@ import { import { LocationSpec } from '@backstage/plugin-catalog-node'; import { GithubDiscoveryProcessor, parseUrl } from './GithubDiscoveryProcessor'; import { getOrganizationRepositories } from '../lib'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib'); const mockGetOrganizationRepositories = @@ -80,7 +80,7 @@ describe('GithubDiscoveryProcessor', () => { const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); const processor = GithubDiscoveryProcessor.fromConfig(config, { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), githubCredentialsProvider, }); const location: LocationSpec = { @@ -105,7 +105,7 @@ describe('GithubDiscoveryProcessor', () => { const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); const processor = GithubDiscoveryProcessor.fromConfig(config, { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), githubCredentialsProvider, }); const location: LocationSpec = { @@ -130,7 +130,7 @@ describe('GithubDiscoveryProcessor', () => { const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); const processor = GithubDiscoveryProcessor.fromConfig(config, { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), githubCredentialsProvider, }); diff --git a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.test.ts b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.test.ts index 195214d561..3e841c3e68 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.test.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { GithubCredentialsProvider, @@ -23,12 +22,13 @@ import { import { LocationSpec } from '@backstage/plugin-catalog-node'; import { graphql } from '@octokit/graphql'; import { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('@octokit/graphql'); describe('GithubOrgReaderProcessor', () => { describe('implementation', () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const integrations = ScmIntegrations.fromConfig( new ConfigReader({ integrations: { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index a201177198..42e49c2f72 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, @@ -25,6 +24,7 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { GithubEntityProvider } from './GithubEntityProvider'; import * as helpers from '../lib/github'; import { EventParams } from '@backstage/plugin-events-node'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib/github', () => { return { @@ -44,10 +44,10 @@ class PersistingTaskRunner implements TaskRunner { } } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('GithubEntityProvider', () => { - afterEach(() => jest.resetAllMocks()); + afterEach(() => jest.clearAllMocks()); it('no provider config', () => { const schedule = new PersistingTaskRunner(); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index c89901fcb3..df49ac9803 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -29,6 +28,7 @@ import { withLocations, } from './GithubMultiOrgEntityProvider'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('@octokit/graphql'); @@ -60,7 +60,7 @@ describe('GithubMultiOrgEntityProvider', () => { refresh: jest.fn(), }; - logger = getVoidLogger(); + logger = mockServices.logger.mock(); gitHubConfig = { host: 'github.com' }; @@ -1016,7 +1016,7 @@ describe('GithubMultiOrgEntityProvider', () => { }; beforeEach(async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); events = DefaultEventsService.create({ logger }); const config = new ConfigReader({ integrations: { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index dade7d7983..82c93b0d57 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { GithubCredentialsProvider, @@ -28,6 +27,7 @@ import { } from '@backstage/plugin-events-node'; import { GithubOrgEntityProvider } from './GithubOrgEntityProvider'; import { withLocations } from '../lib/withLocations'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('@octokit/graphql'); @@ -48,7 +48,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const gitHubConfig = { host: 'https://github.com', }; @@ -269,7 +269,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = DefaultEventsService.create({ logger }); const gitHubConfig: GithubIntegrationConfig = { host: 'github.com', @@ -356,7 +356,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = DefaultEventsService.create({ logger }); const gitHubConfig: GithubIntegrationConfig = { host: 'github.com', @@ -443,7 +443,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = DefaultEventsService.create({ logger }); const gitHubConfig: GithubIntegrationConfig = { host: 'github.com', @@ -536,7 +536,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = DefaultEventsService.create({ logger }); const gitHubConfig: GithubIntegrationConfig = { host: 'github.com', @@ -630,7 +630,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = DefaultEventsService.create({ logger }); const gitHubConfig: GithubIntegrationConfig = { host: 'github.com', @@ -881,7 +881,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = DefaultEventsService.create({ logger }); const gitHubConfig: GithubIntegrationConfig = { host: 'github.com', @@ -1129,7 +1129,7 @@ describe('GithubOrgEntityProvider', () => { refresh: jest.fn(), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = DefaultEventsService.create({ logger }); const gitHubConfig: GithubIntegrationConfig = { host: 'github.com', diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index f836500c6e..c1608cd386 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { rest, RestRequest } from 'msw'; @@ -148,7 +150,7 @@ function getProcessor({ return GitLabDiscoveryProcessor.fromConfig( new ConfigReader(config || getConfig()), { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), ...options, }, ); diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts index 9dfe8f8525..7d40eb1204 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { readGitLabIntegrationConfig } from '@backstage/integration'; import { setupServer } from 'msw/node'; @@ -34,14 +36,14 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(client.isSelfManaged()).toBeTruthy(); }); it('returns false if gitlab.com', () => { const client = new GitLabClient({ config: readGitLabIntegrationConfig(new ConfigReader(mock.config_saas)), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(client.isSelfManaged()).toBeFalsy(); }); @@ -53,7 +55,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const { items } = await client.pagedRequest(mock.paged_endpoint); @@ -66,7 +68,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const requestedPage = 2; @@ -87,7 +89,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const { items, nextPage } = await client.pagedRequest( @@ -106,7 +108,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); // non-200 status code should throw await expect(() => @@ -122,7 +124,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const groupProjectsGen = paginated( @@ -146,7 +148,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const instanceProjects = paginated( @@ -167,7 +169,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const allUsers: GitLabUser[] = []; @@ -188,7 +190,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const allGroups: GitLabGroup[] = []; @@ -207,7 +209,7 @@ describe('GitLabClient', () => { it('gets all users under group', async () => { const client = new GitLabClient({ config: readGitLabIntegrationConfig(new ConfigReader(mock.config_saas)), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const saasMembers = ( await client.getGroupMembers('saas-multi-user-group', [ @@ -221,7 +223,7 @@ describe('GitLabClient', () => { it('gets all users with token without full permissions', async () => { const client = new GitLabClient({ config: readGitLabIntegrationConfig(new ConfigReader(mock.config_saas)), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const saasMembers = ( await client.getGroupMembers('', ['DIRECT, DESCENDANTS']) @@ -231,7 +233,7 @@ describe('GitLabClient', () => { it('rejects when GraphQL returns errors', async () => { const client = new GitLabClient({ config: readGitLabIntegrationConfig(new ConfigReader(mock.config_saas)), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); await expect(() => client.getGroupMembers('error-group', ['DIRECT, DESCENDANTS']), @@ -244,7 +246,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const saasMembers = ( await client.getGroupMembers('multi-page-saas', ['DIRECT, DESCENDANTS']) @@ -262,7 +264,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const allGroups = (await client.listDescendantGroups('group-with-parent')) @@ -277,7 +279,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const allGroups = ( @@ -292,7 +294,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); await expect(() => @@ -306,7 +308,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const saasGroups = (await client.listDescendantGroups('root')).items; @@ -323,7 +325,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const members = await client.getGroupMembers('group1', ['DIRECT']); @@ -346,7 +348,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const members = await client.getGroupMembers('non-existing-group', [ @@ -362,7 +364,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); await expect(() => @@ -377,7 +379,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const members = await client.getGroupMembers('multi-page', ['DIRECT']); @@ -393,7 +395,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const group = await client.getGroupById(1); @@ -405,7 +407,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); await expect(() => client.getGroupById(42)).rejects.toThrow( @@ -420,7 +422,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const project = await client.getProjectById(1); @@ -432,7 +434,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); await expect(() => client.getProjectById(42)).rejects.toThrow( @@ -447,7 +449,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const user = await client.getUserById(1); @@ -459,7 +461,7 @@ describe('GitLabClient', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); await expect(() => client.getUserById(42)).rejects.toThrow( @@ -475,7 +477,7 @@ describe('paginated', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const paginatedItems = paginated( @@ -499,7 +501,7 @@ describe('hasFile', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); }); @@ -528,7 +530,7 @@ describe('pagedRequest search params', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const { items } = await client.pagedRequest<{ endpoint: string }>( @@ -546,7 +548,7 @@ describe('pagedRequest search params', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const { items } = await client.pagedRequest<{ endpoint: string }>( @@ -567,7 +569,7 @@ describe('pagedRequest search params', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const { items } = await client.pagedRequest<{ endpoint: string }>( @@ -588,7 +590,7 @@ describe('pagedRequest search params', () => { config: readGitLabIntegrationConfig( new ConfigReader(mock.config_self_managed), ), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const { items } = await client.pagedRequest<{ endpoint: string }>( diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 7167fc1c2d..febaa00d28 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -14,13 +14,15 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, TaskRunner, } from '@backstage/backend-tasks'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { DefaultEventsService } from '@backstage/plugin-events-node'; @@ -31,7 +33,7 @@ import { GitlabDiscoveryEntityProvider } from './GitlabDiscoveryEntityProvider'; const server = setupServer(...handlers); setupRequestMockHandlers(server); -afterEach(() => jest.resetAllMocks()); +afterEach(() => jest.clearAllMocks()); class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; @@ -46,7 +48,7 @@ class PersistingTaskRunner implements TaskRunner { } } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('GitlabDiscoveryEntityProvider - configuration', () => { it('should not instantiate providers when no config found', () => { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts index b5a049538d..dfbf39267f 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -14,13 +14,15 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, TaskRunner, } from '@backstage/backend-tasks'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { DefaultEventsService } from '@backstage/plugin-events-node'; @@ -32,7 +34,7 @@ import { GitlabOrgDiscoveryEntityProvider } from './GitlabOrgDiscoveryEntityProv const server = setupServer(...handlers); setupRequestMockHandlers(server); -afterEach(() => jest.resetAllMocks()); +afterEach(() => jest.clearAllMocks()); class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; @@ -47,7 +49,7 @@ class PersistingTaskRunner implements TaskRunner { } } -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('GitlabOrgDiscoveryEntityProvider - configuration', () => { it('should not instantiate providers when no config found', () => { diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts index 9416fa27eb..eccfaee655 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TestDatabases } from '@backstage/backend-test-utils'; +import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { IncrementalEntityProvider } from '../types'; import { WrapperProviders } from './WrapperProviders'; @@ -29,13 +28,13 @@ describe('WrapperProviders', () => { ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8'], }); const config = new ConfigReader({}); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const scheduler = { scheduleTask: jest.fn(), }; beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); it.each(databases.eachSupportedId())( diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index 9f4b9ee08c..8821ffd2db 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -25,7 +25,7 @@ import { readMicrosoftGraphUsersInGroups, resolveRelations, } from './read'; -import { getVoidLogger } from '@backstage/backend-common'; +import { mockServices } from '@backstage/backend-test-utils'; function user(data: Partial): UserEntity { return merge( @@ -106,7 +106,7 @@ describe('read microsoft graph', () => { const { users } = await readMicrosoftGraphUsers(client, { userFilter: 'accountEnabled eq true', - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(users).toEqual([ @@ -153,7 +153,7 @@ describe('read microsoft graph', () => { const { users } = await readMicrosoftGraphUsers(client, { queryMode: 'advanced', userFilter: 'accountEnabled eq true', - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(users).toEqual([ @@ -206,7 +206,7 @@ describe('read microsoft graph', () => { metadata: { name: 'x' }, spec: { memberOf: [] }, }), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(users).toEqual([ @@ -246,7 +246,7 @@ describe('read microsoft graph', () => { const { users } = await readMicrosoftGraphUsersInGroups(client, { userGroupMemberFilter: 'securityEnabled eq true', - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(users).toEqual([ @@ -304,7 +304,7 @@ describe('read microsoft graph', () => { const { users } = await readMicrosoftGraphUsersInGroups(client, { queryMode: 'advanced', userGroupMemberFilter: 'securityEnabled eq true', - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(users).toEqual([ @@ -369,7 +369,7 @@ describe('read microsoft graph', () => { metadata: { name: 'x' }, spec: { memberOf: [] }, }), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); expect(users).toEqual([ @@ -893,7 +893,7 @@ describe('read microsoft graph', () => { ); await readMicrosoftGraphOrg(client, 'tenantid', { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), groupFilter: 'securityEnabled eq false', }); @@ -930,7 +930,7 @@ describe('read microsoft graph', () => { ); await readMicrosoftGraphOrg(client, 'tenantid', { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), userExpand: 'manager', userFilter: 'accountEnabled eq true', groupFilter: 'securityEnabled eq false', @@ -970,7 +970,7 @@ describe('read microsoft graph', () => { ); await readMicrosoftGraphOrg(client, 'tenantid', { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), loadUserPhotos: false, }); @@ -1007,7 +1007,7 @@ describe('read microsoft graph', () => { client.getGroupMembers.mockImplementation(getExampleGroupMembers); await readMicrosoftGraphOrg(client, 'tenantid', { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), userSelect: ['mail'], }); @@ -1040,7 +1040,7 @@ describe('read microsoft graph', () => { ); await readMicrosoftGraphOrg(client, 'tenantid', { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), userGroupMemberFilter: 'name eq backstage-group', groupFilter: 'securityEnabled eq false', }); @@ -1089,7 +1089,7 @@ describe('read microsoft graph', () => { ); const { users } = await readMicrosoftGraphOrg(client, 'tenantid', { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), loadUserPhotos: false, }); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index dccf7ed53d..78efa1b3c1 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler, TaskInvocationDefinition, @@ -36,6 +35,7 @@ import { MicrosoftGraphOrgEntityProvider, withLocations, } from './MicrosoftGraphOrgEntityProvider'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../microsoftGraph', () => { return { @@ -98,7 +98,7 @@ describe('MicrosoftGraphOrgEntityProvider', () => { afterEach(() => jest.resetAllMocks()); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const taskRunner = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => taskRunner, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts index a33108c8fd..dfc1ff6bcc 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { MicrosoftGraphClient, readMicrosoftGraphOrg } from '../microsoftGraph'; import { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../microsoftGraph', () => { return { @@ -45,7 +45,7 @@ describe('MicrosoftGraphOrgReaderProcessor', () => { clientSecret: 'clientsecret', }, ], - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); jest diff --git a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts index 78568fd19f..ea165b74a7 100644 --- a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts +++ b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { OpenApiRefProcessor } from './OpenApiRefProcessor'; import { bundleFileWithRefs } from './lib'; +import { mockServices } from '@backstage/backend-test-utils'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; jest.mock('./lib', () => ({ bundleFileWithRefs: jest.fn(), @@ -53,7 +54,7 @@ describe('OpenApiRefProcessor', () => { search: jest.fn(), }; const processor = OpenApiRefProcessor.fromConfig(config, { - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), reader, }); diff --git a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts index 660ddc2bdd..bd76aa2048 100644 --- a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts +++ b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts @@ -16,7 +16,6 @@ import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; -import { getVoidLogger } from '@backstage/backend-common'; import { PuppetDbEntityProvider } from './PuppetDbEntityProvider'; import { DeferredEntity, @@ -30,6 +29,7 @@ import { ResourceEntity, } from '@backstage/catalog-model'; import { DEFAULT_ENTITY_OWNER, ENDPOINT_NODES } from '../puppet/constants'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../puppet/read', () => { return { @@ -37,7 +37,7 @@ jest.mock('../puppet/read', () => { }; }); -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; diff --git a/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts b/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts index e43424b361..4b12a9bf28 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { TestDatabases } from '@backstage/backend-test-utils'; +import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { Entity } from '@backstage/catalog-model'; import { applyDatabaseMigrations } from '../../migrations'; import { @@ -31,7 +30,7 @@ jest.setTimeout(60_000); describe('performStitching', () => { const databases = TestDatabases.create(); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); // NOTE(freben): Testing the deferred path since it's a superset of the immediate one it.each(databases.eachSupportedId())( diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts index 6c2774a6fb..dd019f530f 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { CodeOwnersProcessor } from './CodeOwnersProcessor'; import { LocationSpec } from '@backstage/plugin-catalog-common'; +import { mockServices } from '@backstage/backend-test-utils'; const mockCodeOwnersText = () => ` * @acme/team-foo @acme/team-bar @@ -52,7 +52,7 @@ describe('CodeOwnersProcessor', () => { }), }; const processor = CodeOwnersProcessor.fromConfig(config, { - logger: getVoidLogger(), + logger: mockServices.logger.mock(), reader, }); diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index c9575ce5cd..dd80f8f0c4 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -14,12 +14,11 @@ * limitations under the License. */ +import { UrlReader, UrlReaders } from '@backstage/backend-common'; import { - getVoidLogger, - UrlReader, - UrlReaders, -} from '@backstage/backend-common'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -47,7 +46,7 @@ describe('UrlReaderProcessor', () => { }); it('should load from url', async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const reader = UrlReaders.default({ logger, config: new ConfigReader({ @@ -105,7 +104,7 @@ describe('UrlReaderProcessor', () => { }); it('should use cached data when available', async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const reader = UrlReaders.default({ logger, config: new ConfigReader({ @@ -153,7 +152,7 @@ describe('UrlReaderProcessor', () => { }); it('should fail load from url with error', async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const reader = UrlReaders.default({ logger, config: new ConfigReader({ @@ -191,7 +190,7 @@ describe('UrlReaderProcessor', () => { }); it('uses search when there are globs', async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const reader: jest.Mocked = { readUrl: jest.fn(), diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts index fba5e387a9..3f283a5b38 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { Hash } from 'crypto'; import { DateTime } from 'luxon'; import waitForExpect from 'wait-for-expect'; @@ -23,6 +22,7 @@ import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine import { CatalogProcessingOrchestrator } from './types'; import { Stitcher } from '../stitching/types'; import { ConfigReader } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; describe('DefaultCatalogProcessingEngine', () => { const db = { @@ -64,7 +64,7 @@ describe('DefaultCatalogProcessingEngine', () => { }); const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, @@ -132,7 +132,7 @@ describe('DefaultCatalogProcessingEngine', () => { }); const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, @@ -216,7 +216,7 @@ describe('DefaultCatalogProcessingEngine', () => { const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, @@ -293,7 +293,7 @@ describe('DefaultCatalogProcessingEngine', () => { const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, @@ -352,7 +352,7 @@ describe('DefaultCatalogProcessingEngine', () => { it('should stitch both the previous and new sources when relations change', async () => { const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, @@ -467,7 +467,7 @@ describe('DefaultCatalogProcessingEngine', () => { it('should not stitch sources entities when relations are the same', async () => { const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, @@ -550,7 +550,7 @@ describe('DefaultCatalogProcessingEngine', () => { it('should stitch sources entities when new relation of different type added', async () => { const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, @@ -638,7 +638,7 @@ describe('DefaultCatalogProcessingEngine', () => { it('should stitch sources entities when relation is removed', async () => { const engine = new DefaultCatalogProcessingEngine({ config: new ConfigReader({}), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), processingDatabase: db, knex: {} as any, orchestrator: orchestrator, diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts index fc6855f6df..e8d05e3f25 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -37,6 +36,7 @@ import { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessing import { defaultEntityDataParser } from '../modules/util/parse'; import { ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; +import { mockServices } from '@backstage/backend-test-utils'; class FooBarProcessor implements CatalogProcessor { getProcessorName = () => 'foo-bar'; @@ -93,7 +93,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => { const orchestrator = new DefaultCatalogProcessingOrchestrator({ processors: [new FooBarProcessor()], integrations: ScmIntegrations.fromConfig(new ConfigReader({})), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), parser: defaultEntityDataParser, policy: EntityPolicies.allOf([]), rulesEnforcer: { isAllowed: () => true }, @@ -209,7 +209,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => { processor2 as CatalogProcessor, ], integrations: ScmIntegrations.fromConfig(new ConfigReader({})), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), parser: defaultEntityDataParser, policy: EntityPolicies.allOf([]), rulesEnforcer: { isAllowed: () => true }, @@ -222,7 +222,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => { processor2 as CatalogProcessor, ], integrations: ScmIntegrations.fromConfig(new ConfigReader({})), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), parser: defaultEntityDataParser, policy: EntityPolicies.allOf([]), rulesEnforcer: { isAllowed: () => true }, @@ -287,7 +287,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => { const orchestrator = new DefaultCatalogProcessingOrchestrator({ processors: [processor], integrations, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), parser, policy: EntityPolicies.allOf([]), rulesEnforcer, @@ -329,7 +329,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => { const orchestrator = new DefaultCatalogProcessingOrchestrator({ processors: [processor], integrations, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), parser, policy: EntityPolicies.allOf([new FailingEntityPolicy()]), rulesEnforcer, diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 4d8dc7c7a4..3c98f2a8bc 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { TestDatabaseId, TestDatabases, mockCredentials, + mockServices, } from '@backstage/backend-test-utils'; import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { Knex } from 'knex'; @@ -163,7 +163,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); const result = await catalog.entityAncestry('k:default/root'); @@ -196,7 +196,7 @@ describe('DefaultEntitiesCatalog', () => { await createDatabase(databaseId); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); await expect(() => @@ -242,7 +242,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); const result = await catalog.entityAncestry('k:default/root'); @@ -301,7 +301,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntityToSearch(entity2); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -340,7 +340,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntityToSearch(entity2); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -393,7 +393,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntityToSearch(entity4); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -452,7 +452,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntityToSearch(entity2); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -499,7 +499,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntityToSearch(entity2); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -547,7 +547,7 @@ describe('DefaultEntitiesCatalog', () => { ); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -610,7 +610,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -701,7 +701,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -756,7 +756,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -804,7 +804,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -958,7 +958,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1113,7 +1113,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1174,7 +1174,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1273,7 +1273,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1353,7 +1353,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1382,7 +1382,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1503,7 +1503,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1567,7 +1567,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1718,7 +1718,7 @@ describe('DefaultEntitiesCatalog', () => { const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); await catalog.removeEntityByUid(uid); @@ -1768,7 +1768,7 @@ describe('DefaultEntitiesCatalog', () => { }); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1827,7 +1827,7 @@ describe('DefaultEntitiesCatalog', () => { }); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); @@ -1876,7 +1876,7 @@ describe('DefaultEntitiesCatalog', () => { }); const catalog = new DefaultEntitiesCatalog({ database: knex, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), stitcher, }); diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index a2fae0e8bd..5687bc1f85 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import type { Location } from '@backstage/catalog-client'; @@ -78,7 +77,7 @@ describe('createRouter readonly disabled', () => { entitiesCatalog, locationService, orchestrator, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), refreshService, config: new ConfigReader(undefined), permissionIntegrationRouter: express.Router(), @@ -869,7 +868,7 @@ describe('createRouter readonly enabled', () => { const router = await createRouter({ entitiesCatalog, locationService, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({ catalog: { readonly: true, @@ -1079,7 +1078,7 @@ describe('NextRouter permissioning', () => { const router = await createRouter({ entitiesCatalog, locationService, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), refreshService, config: new ConfigReader(undefined), permissionIntegrationRouter: createPermissionIntegrationRouter({ diff --git a/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts b/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts index 5e7ba7ef71..251aaa9080 100644 --- a/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts +++ b/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { TestDatabases } from '@backstage/backend-test-utils'; +import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { Entity } from '@backstage/catalog-model'; import { applyDatabaseMigrations } from '../database/migrations'; import { @@ -31,7 +30,7 @@ jest.setTimeout(60_000); describe('Stitcher', () => { const databases = TestDatabases.create(); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); it.each(databases.eachSupportedId())( 'runs the happy path for %p', diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index 6ed2188773..0c07b0fd4d 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; import { Entity, EntityPolicies, @@ -55,6 +54,7 @@ import { RefreshOptions, RefreshService } from '../service/types'; import { DefaultStitcher } from '../stitching/DefaultStitcher'; import { mockServices } from '@backstage/backend-test-utils'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { DatabaseManager } from '@backstage/backend-common'; const voidLogger = mockServices.logger.mock(); @@ -223,7 +223,7 @@ class TestHarness { }, }, ); - const logger = options?.logger ?? getVoidLogger(); + const logger = options?.logger ?? mockServices.logger.mock(); const db = options?.db ?? (await DatabaseManager.fromConfig(config, { logger }) diff --git a/plugins/devtools-backend/src/service/router.test.ts b/plugins/devtools-backend/src/service/router.test.ts index fe4f176a04..9f9db0a8d4 100644 --- a/plugins/devtools-backend/src/service/router.test.ts +++ b/plugins/devtools-backend/src/service/router.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import express from 'express'; import request from 'supertest'; @@ -38,7 +37,7 @@ describe('createRouter', () => { beforeAll(async () => { const router = await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({ healthCheck: { endpoint: [ diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts index 60930fdcf7..db4f4ffa32 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts @@ -19,12 +19,12 @@ import { ReceiveMessageCommand, SQSClient, } from '@aws-sdk/client-sqs'; -import { getVoidLogger } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; import { mockClient } from 'aws-sdk-client-mock'; import { AwsSqsConsumingEventPublisher } from './AwsSqsConsumingEventPublisher'; +import { mockServices } from '@backstage/backend-test-utils'; describe('AwsSqsConsumingEventPublisher', () => { it('creates one publisher instance per configured topic', async () => { @@ -52,7 +52,7 @@ describe('AwsSqsConsumingEventPublisher', () => { }, }, }); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = new TestEventsService(); const scheduler = { scheduleTask: jest.fn(), @@ -86,7 +86,7 @@ describe('AwsSqsConsumingEventPublisher', () => { }, }, }); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = new TestEventsService(); const scheduler = { scheduleTask: jest.fn(), @@ -134,7 +134,7 @@ describe('AwsSqsConsumingEventPublisher', () => { }, }, }); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const events = new TestEventsService(); let taskFn: (() => Promise) | undefined = undefined; const scheduler = { diff --git a/plugins/events-backend/src/service/DefaultEventBroker.test.ts b/plugins/events-backend/src/service/DefaultEventBroker.test.ts index 5317251726..f47f2a60bf 100644 --- a/plugins/events-backend/src/service/DefaultEventBroker.test.ts +++ b/plugins/events-backend/src/service/DefaultEventBroker.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { TestEventSubscriber } from '@backstage/plugin-events-backend-test-utils'; import { EventParams, EventSubscriber } from '@backstage/plugin-events-node'; import { DefaultEventBroker } from './DefaultEventBroker'; +import { mockServices } from '@backstage/backend-test-utils'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('DefaultEventBroker', () => { it('passes events to interested subscribers', () => { diff --git a/plugins/events-backend/src/service/EventsBackend.test.ts b/plugins/events-backend/src/service/EventsBackend.test.ts index c2041b57ac..5f9a257ebc 100644 --- a/plugins/events-backend/src/service/EventsBackend.test.ts +++ b/plugins/events-backend/src/service/EventsBackend.test.ts @@ -14,15 +14,16 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { TestEventBroker, TestEventPublisher, TestEventSubscriber, } from '@backstage/plugin-events-backend-test-utils'; import { EventsBackend } from './EventsBackend'; +import { mockServices } from '@backstage/backend-test-utils'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('EventsBackend', () => { it('wires up all components', async () => { @@ -30,7 +31,7 @@ describe('EventsBackend', () => { const publisher1 = new TestEventPublisher(); const publisher2 = new TestEventPublisher(); - await new EventsBackend(logger) + await new EventsBackend(loggerToWinstonLogger(logger)) .setEventBroker(eventBroker) .addPublishers(publisher1, [publisher2]) .addSubscribers(new TestEventSubscriber('one', ['topicA']), [ diff --git a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts index 665ac0b4a9..2bffa4e511 100644 --- a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts +++ b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; import express from 'express'; import Router from 'express-promise-router'; import request from 'supertest'; import { HttpPostIngressEventPublisher } from './HttpPostIngressEventPublisher'; +import { mockServices } from '@backstage/backend-test-utils'; describe('HttpPostIngressEventPublisher', () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); it('should set up routes correctly', async () => { const config = new ConfigReader({ diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index c082da2eab..69028e9860 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -49,6 +49,7 @@ }, "devDependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^" } } diff --git a/plugins/events-node/src/api/DefaultEventsService.test.ts b/plugins/events-node/src/api/DefaultEventsService.test.ts index 33df923892..6e005e9e36 100644 --- a/plugins/events-node/src/api/DefaultEventsService.test.ts +++ b/plugins/events-node/src/api/DefaultEventsService.test.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { DefaultEventsService } from './DefaultEventsService'; import { EventParams } from './EventParams'; +import { mockServices } from '@backstage/backend-test-utils'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('DefaultEventsService', () => { it('passes events to interested subscribers', async () => { diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 15a9d16ee9..f65810ffec 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -43,6 +43,7 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", diff --git a/plugins/example-todo-list-backend/src/service/router.test.ts b/plugins/example-todo-list-backend/src/service/router.test.ts index 3a1ce458f2..e9abb5a76c 100644 --- a/plugins/example-todo-list-backend/src/service/router.test.ts +++ b/plugins/example-todo-list-backend/src/service/router.test.ts @@ -14,19 +14,19 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; import express from 'express'; import request from 'supertest'; import { createRouter } from './router'; +import { mockServices } from '@backstage/backend-test-utils'; describe('createRouter', () => { let app: express.Express; beforeAll(async () => { const router = await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), identity: {} as DefaultIdentityClient, }); app = express().use(router); diff --git a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts index 7e1f99aeeb..3317071893 100644 --- a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.test.ts @@ -15,10 +15,10 @@ */ import { AccessToken, TokenCredential } from '@azure/identity'; -import { getVoidLogger } from '@backstage/backend-common'; import { AzureIdentityStrategy } from './AzureIdentityStrategy'; +import { mockServices } from '@backstage/backend-test-utils'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); class StaticTokenCredential implements TokenCredential { private count: number = 0; diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index 1ac2a49737..64b4bfbd0a 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { Config, ConfigReader } from '@backstage/config'; import { CatalogApi } from '@backstage/catalog-client'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; @@ -65,7 +64,7 @@ describe('getCombinedClusterSupplier', () => { config, catalogApi, mockStrategy, - getVoidLogger(), + mockServices.logger.mock(), undefined, auth, ); @@ -109,7 +108,7 @@ describe('getCombinedClusterSupplier', () => { config, catalogApi, new DispatchStrategy({ authStrategyMap: {} }), - getVoidLogger(), + mockServices.logger.mock(), undefined, auth, ), @@ -119,7 +118,7 @@ describe('getCombinedClusterSupplier', () => { }); it('logs a warning when two clusters have the same name', async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const warn = jest.spyOn(logger, 'warn'); const config: Config = new ConfigReader( { diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index bf735cdb28..a433374a2c 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ClusterDetails, CustomResource, @@ -27,7 +26,10 @@ import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { FetchResponse, KubernetesRequestAuth, @@ -189,7 +191,7 @@ describe('KubernetesFanOutHandler', () => { const getKubernetesFanOutHandler = (customResources: CustomResource[]) => { return new KubernetesFanOutHandler({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), fetcher: { fetchObjectsForService, fetchPodMetricsByNamespaces, @@ -1169,7 +1171,7 @@ describe('KubernetesFanOutHandler', () => { ], }), }; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const kubernetesFanOutHandler = new KubernetesFanOutHandler({ logger, fetcher: new KubernetesClientBasedFetcher({ logger }), diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 3b435fad74..dafb073bb1 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -15,7 +15,6 @@ */ import { ANNOTATION_KUBERNETES_AUTH_PROVIDER } from '@backstage/plugin-kubernetes-common'; -import { getVoidLogger } from '@backstage/backend-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { ObjectToFetch } from '../types/types'; import { @@ -28,6 +27,7 @@ import { import { setupServer } from 'msw/node'; import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import { Config } from '@kubernetes/client-node'; @@ -115,7 +115,7 @@ describe('KubernetesFetcher', () => { describe('fetchObjectsForService', () => { let sut: KubernetesClientBasedFetcher; - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const testErrorResponse = async ( errorResponse: any, @@ -1095,7 +1095,7 @@ describe('KubernetesFetcher', () => { beforeEach(() => { sut = new KubernetesClientBasedFetcher({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index a22642b543..02eeb8c7ed 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -16,9 +16,10 @@ import 'buffer'; import { resolve as resolvePath } from 'path'; -import { errorHandler, getVoidLogger } from '@backstage/backend-common'; +import { errorHandler } from '@backstage/backend-common'; import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import { NotFoundError } from '@backstage/errors'; @@ -70,7 +71,7 @@ describe('KubernetesProxy', () => { let proxy: KubernetesProxy; let authStrategy: jest.Mocked; const worker = setupServer(); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); const clusterSupplier: jest.Mocked = { getClusters: jest.fn< @@ -549,7 +550,7 @@ describe('KubernetesProxy', () => { }; proxy = new KubernetesProxy({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), clusterSupplier: clusterSupplier, authStrategy: strategy, discovery: mockDisocveryApi, @@ -671,7 +672,7 @@ describe('KubernetesProxy', () => { it('returns a response with a localKubectlProxy auth provider configuration', async () => { proxy = new KubernetesProxy({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), clusterSupplier: new LocalKubectlProxyClusterLocator(), authStrategy: new AnonymousStrategy(), discovery: mockDisocveryApi, diff --git a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts index 3cb25d7934..fdfea030a5 100644 --- a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts @@ -32,15 +32,15 @@ import { KubernetesRequestAuth, } from '@backstage/plugin-kubernetes-common'; import { PinnipedHelper, PinnipedParameters } from './PinnipedHelper'; -import { getVoidLogger } from '@backstage/backend-common'; import { HEADER_KUBERNETES_CLUSTER } from '@backstage/plugin-kubernetes-backend'; import { JsonObject } from '@backstage/types'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; describe('Pinniped - tokenCredentialRequest', () => { let app: ExtendedHttpServer; - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); let httpsRequest: jest.SpyInstance; const worker = setupServer(); setupRequestMockHandlers(worker); diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 3f091bac4b..804b0ff088 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -15,7 +15,6 @@ */ import { DatabaseManager, - getVoidLogger, PluginDatabaseManager, } from '@backstage/backend-common'; import express from 'express'; @@ -53,7 +52,7 @@ describe('createRouter', () => { beforeAll(async () => { const router = await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), database: createDatabase(), discovery, signals: signalService, diff --git a/plugins/permission-backend/src/service/router.test.ts b/plugins/permission-backend/src/service/router.test.ts index 0da9ecd748..be90c5f628 100644 --- a/plugins/permission-backend/src/service/router.test.ts +++ b/plugins/permission-backend/src/service/router.test.ts @@ -16,7 +16,6 @@ import express from 'express'; import request from 'supertest'; -import { getVoidLogger } from '@backstage/backend-common'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { ApplyConditionsRequestEntry, @@ -67,7 +66,7 @@ describe('createRouter', () => { beforeAll(async () => { const router = await createRouter({ config: new ConfigReader({ permission: { enabled: true } }), - logger: getVoidLogger(), + logger: mockServices.logger.mock(), discovery: mockServices.discovery(), auth: mockServices.auth(), httpAuth: mockServices.httpAuth({ diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 8f050bcfff..215f624fe9 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -29,7 +29,6 @@ import { } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { - getVoidLogger, PluginEndpointDiscovery, ServerTokenManager, } from '@backstage/backend-common'; @@ -62,7 +61,7 @@ const config = new ConfigReader({ permission: { enabled: true }, backend: { auth: { keys: [{ secret: 'a-secret-key' }] } }, }); -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); describe('ServerPermissionClient', () => { setupRequestMockHandlers(server); diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index 8643ee67c0..282f2cfe96 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { getVoidLogger, HostDiscovery } from '@backstage/backend-common'; +import { + HostDiscovery, + loggerToWinstonLogger, +} from '@backstage/backend-common'; import { ConfigSources, MutableConfigSource, @@ -25,6 +28,7 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import request from 'supertest'; import { createRouter } from './router'; +import { mockServices } from '@backstage/backend-test-utils'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the rest of the tests @@ -57,7 +61,7 @@ describe('createRouter reloadable configuration', () => { afterEach(() => server.resetHandlers()); it('should be able to observe the config', async () => { - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); // Grab the subscriber function and use mutable config data to mock a config file change const mutableConfigSource = MutableConfigSource.create({ data: {} }); diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index 9149f36015..8dcabed16b 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { getVoidLogger, HostDiscovery } from '@backstage/backend-common'; +import { + HostDiscovery, + loggerToWinstonLogger, +} from '@backstage/backend-common'; import { mockServices } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { Request, Response } from 'express'; @@ -37,7 +40,7 @@ const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction< describe('createRouter', () => { describe('where all proxy config are valid', () => { - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); const config = new ConfigReader({ backend: { baseUrl: 'https://example.com:7007', @@ -130,7 +133,7 @@ describe('createRouter', () => { describe('where buildMiddleware would fail', () => { it('throws an error if skip failures is not set', async () => { - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); logger.warn = jest.fn(); const config = new ConfigReader({ backend: { @@ -165,7 +168,7 @@ describe('createRouter', () => { }); it('works if skip failures is set', async () => { - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); logger.warn = jest.fn(); const config = new ConfigReader({ backend: { @@ -201,7 +204,7 @@ describe('createRouter', () => { }); describe('buildMiddleware', () => { - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); beforeEach(() => { mockCreateProxyMiddleware.mockClear(); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts index 18ff0ad3ec..da82849c1e 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ import { createConfluenceToMarkdownAction } from './confluenceToMarkdown'; -import { getVoidLogger } from '@backstage/backend-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import { rest } from 'msw'; @@ -57,7 +57,7 @@ describe('confluence:transform:markdown examples', () => { repoUrl: string; }>; - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); jest.spyOn(logger, 'info'); const mockDir = createMockDirectory(); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts index eccb66f79e..b19259b3df 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts @@ -15,12 +15,12 @@ */ import { createConfluenceToMarkdownAction } from './confluenceToMarkdown'; -import { getVoidLogger } from '@backstage/backend-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createMockDirectory, + mockServices, setupRequestMockHandlers, } from '@backstage/backend-test-utils'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; @@ -56,7 +56,7 @@ describe('confluence:transform:markdown', () => { repoUrl: string; }>; - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); jest.spyOn(logger, 'info'); const mockDir = createMockDirectory(); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts index 691cebd367..2ec4effe56 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts @@ -53,12 +53,13 @@ const mockGit = { }; jest.mock('@backstage/backend-common', () => ({ + loggerToWinstonLogger: jest.requireActual('@backstage/backend-common') + .loggerToWinstonLogger, Git: { fromAuth() { return mockGit; }, }, - getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, })); jest.mock('./helpers', () => { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts index 206d4f067d..4d16a25774 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts @@ -27,12 +27,13 @@ const mockGit = { }; jest.mock('@backstage/backend-common', () => ({ + loggerToWinstonLogger: jest.requireActual('@backstage/backend-common') + .loggerToWinstonLogger, Git: { fromAuth() { return mockGit; }, }, - getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, })); jest.mock('./gitHelpers', () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 2e19da669f..5d70e5bfba 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { TemplateActionRegistry } from '../actions'; import { ScmIntegrations } from '@backstage/integration'; @@ -37,11 +36,14 @@ import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-co import { createMockDirectory, mockCredentials, + mockServices, } from '@backstage/backend-test-utils'; import stripAnsi from 'strip-ansi'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; +import { LoggerService } from '@backstage/backend-plugin-api'; describe('NunjucksWorkflowRunner', () => { - const logger = getVoidLogger(); + let logger: LoggerService; let actionRegistry = new TemplateActionRegistry(); let runner: NunjucksWorkflowRunner; let fakeActionHandler: jest.Mock; @@ -94,6 +96,7 @@ describe('NunjucksWorkflowRunner', () => { mockDir.clear(); jest.resetAllMocks(); + logger = mockServices.logger.mock(); actionRegistry = new TemplateActionRegistry(); fakeActionHandler = jest.fn(); fakeTaskLog = jest.fn(); @@ -173,7 +176,7 @@ describe('NunjucksWorkflowRunner', () => { actionRegistry, integrations, workingDirectory: mockDir.path, - logger, + logger: loggerToWinstonLogger(logger), permissions: mockedPermissionApi, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index f4bae43dbf..ec2a81680d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { getVoidLogger, DatabaseManager } from '@backstage/backend-common'; +import { + DatabaseManager, + loggerToWinstonLogger, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { @@ -23,6 +26,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker, TaskManager } from './StorageTaskBroker'; +import { mockServices } from '@backstage/backend-test-utils'; async function createStore(): Promise { const manager = DatabaseManager.fromConfig( @@ -55,7 +59,7 @@ describe('StorageTaskBroker', () => { secrets: fakeSecrets, }; - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); it('should claim a dispatched work item', async () => { const broker = new StorageTaskBroker(storage, logger); await broker.dispatch(emptyTaskSpec); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 70308c420c..013d5d3296 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -15,7 +15,10 @@ */ import os from 'os'; -import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; +import { + DatabaseManager, + loggerToWinstonLogger, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; @@ -31,6 +34,7 @@ import { import { WorkflowRunner } from './types'; import ObservableImpl from 'zen-observable'; import waitForExpect from 'wait-for-expect'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('./NunjucksWorkflowRunner'); const MockedNunjucksWorkflowRunner = @@ -74,7 +78,7 @@ describe('TaskWorker', () => { MockedNunjucksWorkflowRunner.mockImplementation(() => workflowRunner); }); - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); it('should call the default workflow runner when the apiVersion is beta3', async () => { const broker = new StorageTaskBroker(storage, logger); @@ -167,7 +171,7 @@ describe('Concurrent TaskWorker', () => { MockedNunjucksWorkflowRunner.mockImplementation(() => workflowRunner); }); - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); it('should be able to run multiple tasks at once', async () => { const broker = new StorageTaskBroker(storage, logger); @@ -228,7 +232,7 @@ describe('Cancellable TaskWorker', () => { MockedNunjucksWorkflowRunner.mockImplementation(() => workflowRunner); }); - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); it('should be able to cancel the running task', async () => { const taskBroker = new StorageTaskBroker(storage, logger); diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 2118f46b22..4d04699f2a 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -16,7 +16,7 @@ import { DatabaseManager, - getVoidLogger, + loggerToWinstonLogger, PluginDatabaseManager, UrlReaders, } from '@backstage/backend-common'; @@ -76,7 +76,7 @@ function createDatabase(): PluginDatabaseManager { } const mockUrlReader = UrlReaders.default({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({}), }); @@ -183,7 +183,7 @@ describe('createRouter', () => { describe('not providing an identity api', () => { beforeEach(async () => { - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); const databaseTaskStore = await DatabaseTaskStore.create({ database: createDatabase(), }); @@ -238,7 +238,7 @@ describe('createRouter', () => { }); afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); describe('GET /v2/actions', () => { @@ -690,7 +690,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ describe('providing an identity api', () => { beforeEach(async () => { - const logger = getVoidLogger(); + const logger = loggerToWinstonLogger(mockServices.logger.mock()); const databaseTaskStore = await DatabaseTaskStore.create({ database: createDatabase(), }); diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts index 15ad5eb769..b178a1d51d 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts @@ -15,10 +15,11 @@ */ import { PassThrough } from 'stream'; -import { getVoidLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { createMockDirectory, mockCredentials, + mockServices, } from '@backstage/backend-test-utils'; import { JsonObject } from '@backstage/types'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; @@ -37,7 +38,7 @@ export const createMockActionContext = < ): ActionContext => { const credentials = mockCredentials.user(); const defaultContext = { - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), logStream: new PassThrough(), output: jest.fn(), createTemporaryDirectory: jest.fn(), diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts index 64281d660f..85fb9ba2e6 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { Git } from '../scm'; import { commitAndPushRepo, @@ -24,6 +24,7 @@ import { createBranch, cloneRepo, } from './gitHelpers'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../scm', () => ({ Git: { @@ -40,14 +41,13 @@ jest.mock('../scm', () => ({ clone: jest.fn(), }), }, - getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, })); jest.mock('fs-extra', () => ({ cpSync: jest.fn(), })); const mockedGit = Git.fromAuth({ - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }); describe('initRepoAndPush', () => { @@ -64,7 +64,7 @@ describe('initRepoAndPush', () => { username: 'test-user', password: 'test-password', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }); }); @@ -120,7 +120,7 @@ describe('initRepoAndPush', () => { auth: { token: 'test-token', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }); expect(mockedGit.init).toHaveBeenCalledWith({ @@ -138,7 +138,7 @@ describe('initRepoAndPush', () => { username: 'test-user', password: 'test-password', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }); expect(mockedGit.init).toHaveBeenCalledWith({ @@ -159,7 +159,7 @@ describe('initRepoAndPush', () => { username: 'test-user', password: 'test-password', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }); expect(mockedGit.commit).toHaveBeenCalledWith({ @@ -190,7 +190,7 @@ describe('commitAndPushRepo', () => { username: 'test-user', password: 'test-password', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), commitMessage: 'commit message', }); }); @@ -246,7 +246,7 @@ describe('commitAndPushRepo', () => { username: 'test-user', password: 'test-password', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), commitMessage: 'commit message', branch: 'otherbranch', }); @@ -269,7 +269,7 @@ describe('commitAndPushRepo', () => { username: 'test-user', password: 'test-password', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), commitMessage: 'commit message', remoteRef: 'refs/for/master', }); @@ -297,7 +297,7 @@ describe('commitAndPushRepo', () => { username: 'test-user', password: 'test-password', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), branch: 'master', }); @@ -560,7 +560,7 @@ describe('commitAndPushBranch', () => { name: 'gitCommitter', email: 'gitCommitter@backstage.io', }, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }); expect(mockedGit.commit).toHaveBeenCalledWith({ diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 128bc0e28c..1cd9bfc854 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -62,6 +62,7 @@ }, "devDependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@elastic/elasticsearch-mock": "^1.0.0", "@short.io/opensearch-mock": "^0.3.1" diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts index 602a158b98..3f64e0b768 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { errors } from '@elastic/elasticsearch'; import Mock from '@elastic/elasticsearch-mock'; @@ -26,6 +25,7 @@ import { encodePageCursor, } from './ElasticSearchSearchEngine'; import { ElasticSearchSearchEngineIndexer } from './ElasticSearchSearchEngineIndexer'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('uuid', () => ({ v4: () => 'tag' })); @@ -86,14 +86,14 @@ describe('ElasticSearchSearchEngine', () => { options, 'search', '', - getVoidLogger(), + mockServices.logger.mock(), 1000, ); inspectableSearchEngine = new ElasticSearchSearchEngineForTranslatorTests( options, 'search', '', - getVoidLogger(), + mockServices.logger.mock(), 1000, ); // eslint-disable-next-line dot-notation @@ -915,7 +915,7 @@ describe('ElasticSearchSearchEngine', () => { const getOptional = jest.spyOn(config, 'getOptional'); await ElasticSearchSearchEngine.fromConfig({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config, }); @@ -940,7 +940,7 @@ describe('ElasticSearchSearchEngine', () => { expect( async () => await ElasticSearchSearchEngine.fromConfig({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config, }), ).not.toThrow(); diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts index 453a825574..44d33c27a5 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { TestPipeline } from '@backstage/plugin-search-backend-node'; import Mock from '@elastic/elasticsearch-mock'; import { range } from 'lodash'; import { ElasticSearchClientWrapper } from './ElasticSearchClientWrapper'; import { ElasticSearchSearchEngineIndexer } from './ElasticSearchSearchEngineIndexer'; +import { mockServices } from '@backstage/backend-test-utils'; const mock = new Mock(); const clientWrapper = ElasticSearchClientWrapper.fromClientOptions({ @@ -43,7 +43,7 @@ describe('ElasticSearchSearchEngineIndexer', () => { indexPrefix: '', indexSeparator: '-index__', alias: 'some-type-index__search', - logger: getVoidLogger(), + logger: mockServices.logger.mock(), elasticSearchClientWrapper: clientWrapper, batchSize: 1000, skipRefresh: false, @@ -270,7 +270,7 @@ describe('ElasticSearchSearchEngineIndexer', () => { indexPrefix: '', indexSeparator: '-index__', alias: 'some-type-index__search', - logger: getVoidLogger(), + logger: mockServices.logger.mock(), elasticSearchClientWrapper: mockClientWrapper, batchSize: 1000, skipRefresh: false, @@ -292,7 +292,7 @@ describe('ElasticSearchSearchEngineIndexer', () => { indexPrefix: '', indexSeparator: '-index__', alias: 'some-type-index__search', - logger: getVoidLogger(), + logger: mockServices.logger.mock(), elasticSearchClientWrapper: clientWrapper, batchSize: 1000, skipRefresh: true, diff --git a/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts b/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts index 498a4d9fbf..82305d0d86 100644 --- a/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts +++ b/plugins/search-backend-module-explore/src/collators/ToolDocumentCollatorFactory.test.ts @@ -14,19 +14,21 @@ * limitations under the License. */ import { - getVoidLogger, PluginEndpointDiscovery, TokenManager, } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { TestPipeline } from '@backstage/plugin-search-backend-node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { Readable } from 'stream'; import { ToolDocumentCollatorFactory } from './ToolDocumentCollatorFactory'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const mockTools = { tools: [ diff --git a/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts b/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts index 5d2f65923c..f4d4a77c3c 100644 --- a/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts +++ b/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.test.ts @@ -13,19 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { StackOverflowQuestionsCollatorFactory, StackOverflowQuestionsCollatorFactoryOptions, } from './StackOverflowQuestionsCollatorFactory'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { TestPipeline } from '@backstage/plugin-search-backend-node'; import { ConfigReader } from '@backstage/config'; import { Readable } from 'stream'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const mockQuestion = { items: [ diff --git a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts index 8f56a92096..26329e5ed6 100644 --- a/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts +++ b/plugins/search-backend-module-techdocs/src/collators/DefaultTechDocsCollatorFactory.test.ts @@ -14,14 +14,16 @@ * limitations under the License. */ import { - getVoidLogger, PluginEndpointDiscovery, TokenManager, } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { TestPipeline } from '@backstage/plugin-search-backend-node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { Readable } from 'stream'; @@ -29,7 +31,7 @@ import { DefaultTechDocsCollatorFactory } from './DefaultTechDocsCollatorFactory import { defaultTechDocsCollatorEntityTransformer } from './defaultTechDocsCollatorEntityTransformer'; import { TechDocsCollatorEntityTransformer } from './TechDocsCollatorEntityTransformer'; -const logger = getVoidLogger(); +const logger = mockServices.logger.mock(); const mockSearchDocIndex = { config: { @@ -89,8 +91,8 @@ describe('DefaultTechDocsCollatorFactory', () => { authenticate: jest.fn(), }; const options = { + logger, discovery: mockDiscoveryApi, - logger: getVoidLogger(), tokenManager: mockTokenManager, }; diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index a3c9e16f95..cd2981d792 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -60,6 +60,7 @@ }, "devDependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/ndjson": "^2.0.1" } diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts index 70d00f6974..6796c7c01e 100644 --- a/plugins/search-backend-node/src/IndexBuilder.test.ts +++ b/plugins/search-backend-node/src/IndexBuilder.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; import { DocumentCollatorFactory, @@ -23,6 +22,7 @@ import { import { Readable, Transform } from 'stream'; import { IndexBuilder } from './IndexBuilder'; import { LunrSearchEngine, SearchEngine } from './index'; +import { mockServices } from '@backstage/backend-test-utils'; class TestDocumentCollatorFactory implements DocumentCollatorFactory { readonly type: string = 'anything'; @@ -57,7 +57,7 @@ describe('IndexBuilder', () => { let testScheduledTaskRunner: TaskRunner; beforeEach(() => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); testScheduledTaskRunner = { run: async (task: TaskInvocationDefinition & { fn: () => void }) => { task.fn(); diff --git a/plugins/search-backend-node/src/Scheduler.test.ts b/plugins/search-backend-node/src/Scheduler.test.ts index 6daae3013d..f7e68988d2 100644 --- a/plugins/search-backend-node/src/Scheduler.test.ts +++ b/plugins/search-backend-node/src/Scheduler.test.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { Scheduler } from './index'; +import { mockServices } from '@backstage/backend-test-utils'; describe('Scheduler', () => { let testScheduler: Scheduler; beforeEach(() => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); testScheduler = new Scheduler({ logger, }); diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts index addcb87497..45cc98e9ba 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.test.ts @@ -15,7 +15,6 @@ */ import { - getVoidLogger, ReadUrlResponse, UrlReader, UrlReaders, @@ -24,10 +23,11 @@ import { ConfigReader } from '@backstage/config'; import { Readable } from 'stream'; import { NewlineDelimitedJsonCollatorFactory } from './NewlineDelimitedJsonCollatorFactory'; import { TestPipeline } from '../test-utils'; +import { mockServices } from '@backstage/backend-test-utils'; describe('DefaultCatalogCollatorFactory', () => { const config = new ConfigReader({}); - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); it('has expected type', () => { const factory = NewlineDelimitedJsonCollatorFactory.fromConfig(config, { diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index e5347b3cb1..407fa779ab 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import lunr from 'lunr'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { @@ -27,6 +26,7 @@ import { import { LunrSearchEngineIndexer } from './LunrSearchEngineIndexer'; import { SearchEngine } from '../types'; import { TestPipeline } from '../test-utils'; +import { mockServices } from '@backstage/backend-test-utils'; /** * Just used to test the default translator shipped with LunrSearchEngine. @@ -77,7 +77,9 @@ describe('LunrSearchEngine', () => { let testLunrSearchEngine: SearchEngine; beforeEach(() => { - testLunrSearchEngine = new LunrSearchEngine({ logger: getVoidLogger() }); + testLunrSearchEngine = new LunrSearchEngine({ + logger: mockServices.logger.mock(), + }); jest.clearAllMocks(); }); @@ -107,7 +109,7 @@ describe('LunrSearchEngine', () => { it('should return translated query', async () => { const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const translatorUnderTest = inspectableSearchEngine.getTranslator(); @@ -149,7 +151,7 @@ describe('LunrSearchEngine', () => { it('should have default offset and limit', async () => { const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const translatorUnderTest = inspectableSearchEngine.getTranslator(); @@ -190,7 +192,7 @@ describe('LunrSearchEngine', () => { it('should return translated query with 1 filter', async () => { const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const translatorUnderTest = inspectableSearchEngine.getTranslator(); @@ -235,7 +237,7 @@ describe('LunrSearchEngine', () => { it('should handle single-item array filter as scalar value', async () => { const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const translatorUnderTest = inspectableSearchEngine.getTranslator(); @@ -266,7 +268,7 @@ describe('LunrSearchEngine', () => { it('should return translated query with multiple filters', async () => { const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const translatorUnderTest = inspectableSearchEngine.getTranslator(); @@ -315,7 +317,7 @@ describe('LunrSearchEngine', () => { it('should throw if translated query references missing field', async () => { const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const translatorUnderTest = inspectableSearchEngine.getTranslator(); @@ -477,7 +479,7 @@ describe('LunrSearchEngine', () => { it('should perform search query and return highlight metadata on match', async () => { const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); const mockDocuments = [ @@ -1012,7 +1014,7 @@ describe('LunrSearchEngine', () => { // Set up an inspectable search engine to pre-set some data. const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); inspectableSearchEngine.setDocStore({ 'existing-location': doc }); @@ -1047,7 +1049,7 @@ describe('LunrSearchEngine', () => { // Set up an inspectable search engine to pre-set some data. const doc = { title: 'A doc', text: 'test', location: 'some-location' }; const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); inspectableSearchEngine.setDocStore({ 'existing-location': doc }); @@ -1073,7 +1075,7 @@ describe('LunrSearchEngine', () => { // Set up an inspectable search engine to pre-set some data. const doc = { title: 'A doc', text: 'test', location: 'some-location' }; const inspectableSearchEngine = new LunrSearchEngineForTests({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), }); inspectableSearchEngine.setDocStore({ 'existing-location': doc }); @@ -1178,7 +1180,9 @@ describe('stopword testing', () => { let testLunrSearchEngine: SearchEngine; beforeEach(() => { - testLunrSearchEngine = new LunrSearchEngine({ logger: getVoidLogger() }); + testLunrSearchEngine = new LunrSearchEngine({ + logger: mockServices.logger.mock(), + }); jest.clearAllMocks(); }); diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 59f2f95afe..81b45bc7e6 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -14,10 +14,7 @@ * limitations under the License. */ -import { - PluginEndpointDiscovery, - getVoidLogger, -} from '@backstage/backend-common'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -55,7 +52,7 @@ describe('createRouter', () => { }; beforeAll(async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); mockSearchEngine = { getIndexer: jest.fn(), setTranslator: jest.fn(), @@ -259,7 +256,7 @@ describe('createRouter', () => { describe('search result filtering', () => { beforeAll(async () => { - const logger = getVoidLogger(); + const logger = mockServices.logger.mock(); mockSearchEngine = { getIndexer: jest.fn(), setTranslator: jest.fn(), diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index b148d4d5e5..846d77d661 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", diff --git a/plugins/signals-backend/src/service/SignalManager.test.ts b/plugins/signals-backend/src/service/SignalManager.test.ts index 7d3a6ddf59..eccc715d1b 100644 --- a/plugins/signals-backend/src/service/SignalManager.test.ts +++ b/plugins/signals-backend/src/service/SignalManager.test.ts @@ -16,8 +16,8 @@ import { WebSocket } from 'ws'; import { EventsServiceSubscribeOptions } from '@backstage/plugin-events-node'; import { SignalManager } from './SignalManager'; -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; class MockWebSocket { closed: boolean = false; @@ -76,7 +76,7 @@ describe('SignalManager', () => { const manager = SignalManager.create({ events: mockEvents, - logger: getVoidLogger(), + logger: mockServices.logger.mock(), config: new ConfigReader({}), lifecycle: mockLifecycle as any, }); diff --git a/plugins/signals-backend/src/service/router.test.ts b/plugins/signals-backend/src/service/router.test.ts index c081031c54..000afec23c 100644 --- a/plugins/signals-backend/src/service/router.test.ts +++ b/plugins/signals-backend/src/service/router.test.ts @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - getVoidLogger, - PluginEndpointDiscovery, -} from '@backstage/backend-common'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; @@ -25,6 +22,7 @@ import { EventsService } from '@backstage/plugin-events-node'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { UserInfoService } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; +import { mockServices } from '@backstage/backend-test-utils'; const eventsServiceMock: jest.Mocked = { subscribe: jest.fn(), @@ -49,7 +47,7 @@ describe('createRouter', () => { beforeAll(async () => { const router = await createRouter({ - logger: getVoidLogger(), + logger: mockServices.logger.mock(), identity: identityApiMock, events: eventsServiceMock, discovery, diff --git a/plugins/techdocs-backend/src/cache/TechDocsCache.test.ts b/plugins/techdocs-backend/src/cache/TechDocsCache.test.ts index fa4c81682c..7dff7069db 100644 --- a/plugins/techdocs-backend/src/cache/TechDocsCache.test.ts +++ b/plugins/techdocs-backend/src/cache/TechDocsCache.test.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import { CacheClient, getVoidLogger } from '@backstage/backend-common'; +import { CacheClient, loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { CacheInvalidationError, TechDocsCache } from './TechDocsCache'; +import { mockServices } from '@backstage/backend-test-utils'; const cached = (str: string): string => { return Buffer.from(str).toString('base64'); @@ -35,7 +36,7 @@ describe('TechDocsCache', () => { }; CacheUnderTest = TechDocsCache.fromConfig(new ConfigReader({}), { cache: MockClient, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }); }); @@ -82,7 +83,7 @@ describe('TechDocsCache', () => { }), { cache: MockClient, - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), }, ); diff --git a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts index 4dd993a163..1d814c3caa 100644 --- a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts +++ b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; import { createCacheMiddleware } from './cacheMiddleware'; import { TechDocsCache } from './TechDocsCache'; +import { mockServices } from '@backstage/backend-test-utils'; /** * Mocks cached HTTP response. @@ -57,7 +58,7 @@ describe('createCacheMiddleware', () => { invalidateMultiple: jest.fn().mockResolvedValue(undefined), } as unknown as jest.Mocked; const router = await createCacheMiddleware({ - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), cache, }); app = express().use(router); diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts index 3f1a857c4d..a0fc908ee5 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts @@ -16,17 +16,20 @@ import { PluginEndpointDiscovery, - getVoidLogger, TokenManager, + loggerToWinstonLogger, } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { DefaultTechDocsCollator } from './DefaultTechDocsCollator'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import { ConfigReader } from '@backstage/config'; -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); const mockSearchDocIndex = { config: { diff --git a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts index ff714eee63..bb66c303b7 100644 --- a/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts +++ b/plugins/techdocs-backend/src/service/DocsSynchronizer.test.ts @@ -15,7 +15,7 @@ */ import { - getVoidLogger, + loggerToWinstonLogger, PluginEndpointDiscovery, } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; @@ -30,6 +30,7 @@ import * as winston from 'winston'; import { TechDocsCache } from '../cache'; import { DocsBuilder, shouldCheckForUpdate } from '../DocsBuilder'; import { DocsSynchronizer, DocsSynchronizerSyncOpts } from './DocsSynchronizer'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../DocsBuilder'); jest.useFakeTimers(); @@ -97,7 +98,7 @@ describe('DocsSynchronizer', () => { docsSynchronizer = new DocsSynchronizer({ publisher, config: new ConfigReader({}), - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), buildLogTransport: mockBuildLogTransport, scmIntegrations: ScmIntegrations.fromConfig(new ConfigReader({})), cache, @@ -346,7 +347,7 @@ describe('DocsSynchronizer', () => { config: new ConfigReader({ techdocs: { legacyUseCaseSensitiveTripletPaths: true }, }), - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), buildLogTransport: new winston.transports.Stream({ stream: new PassThrough(), }), diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index 224a87cb43..72da3023b0 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -16,7 +16,7 @@ import { errorHandler, - getVoidLogger, + loggerToWinstonLogger, PluginCacheManager, PluginEndpointDiscovery, } from '@backstage/backend-common'; @@ -33,6 +33,7 @@ import { DocsSynchronizer, DocsSynchronizerSyncOpts } from './DocsSynchronizer'; import { CachedEntityLoader } from './CachedEntityLoader'; import { createEventStream, createRouter, RouterOptions } from './router'; import { TechDocsCache } from '../cache'; +import { mockServices } from '@backstage/backend-test-utils'; jest.mock('@backstage/catalog-client'); jest.mock('@backstage/config'); @@ -126,7 +127,7 @@ describe('createRouter', () => { generators, publisher, config: new ConfigReader({}), - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), discovery, cache, docsBuildStrategy, @@ -134,7 +135,7 @@ describe('createRouter', () => { const recommendedOptions = { publisher, config: new ConfigReader({}), - logger: getVoidLogger(), + logger: loggerToWinstonLogger(mockServices.logger.mock()), discovery, cache, docsBuildStrategy, diff --git a/plugins/techdocs-node/src/stages/generate/generators.test.ts b/plugins/techdocs-node/src/stages/generate/generators.test.ts index a1c585e795..3e0571af54 100644 --- a/plugins/techdocs-node/src/stages/generate/generators.test.ts +++ b/plugins/techdocs-node/src/stages/generate/generators.test.ts @@ -14,12 +14,16 @@ * limitations under the License. */ -import { ContainerRunner, getVoidLogger } from '@backstage/backend-common'; +import { + ContainerRunner, + loggerToWinstonLogger, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Generators } from './generators'; import { TechdocsGenerator } from './techdocs'; +import { mockServices } from '@backstage/backend-test-utils'; -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); const mockEntity = { apiVersion: 'version', diff --git a/plugins/techdocs-node/src/stages/generate/helpers.test.ts b/plugins/techdocs-node/src/stages/generate/helpers.test.ts index 1b47cfb05c..3589b71c60 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.test.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.test.ts @@ -14,10 +14,12 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import path, { resolve as resolvePath } from 'path'; import { ParsedLocationAnnotation } from '../../helpers'; @@ -35,6 +37,7 @@ import { patchMkdocsYmlWithPlugins, } from './mkdocsPatchers'; import yaml from 'js-yaml'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; const mockEntity = { apiVersion: 'version', @@ -90,7 +93,7 @@ const mkdocsYmlWithAdditionalPluginsWithConfig = fs.readFileSync( const mkdocsYmlWithEnvTag = fs.readFileSync( resolvePath(__filename, '../__fixtures__/mkdocs_with_env_tag.yml'), ); -const mockLogger = getVoidLogger(); +const mockLogger = loggerToWinstonLogger(mockServices.logger.mock()); const warn = jest.spyOn(mockLogger, 'warn'); const scmIntegrations = ScmIntegrations.fromConfig(new ConfigReader({})); diff --git a/plugins/techdocs-node/src/stages/prepare/dir.test.ts b/plugins/techdocs-node/src/stages/prepare/dir.test.ts index 164cfde687..5fbd56264e 100644 --- a/plugins/techdocs-node/src/stages/prepare/dir.test.ts +++ b/plugins/techdocs-node/src/stages/prepare/dir.test.ts @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getVoidLogger, UrlReader } from '@backstage/backend-common'; +import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { DirectoryPreparer } from './dir'; +import { mockServices } from '@backstage/backend-test-utils'; function normalizePath(path: string) { return path @@ -28,7 +29,7 @@ jest.mock('../../helpers', () => ({ ...jest.requireActual<{}>('../../helpers'), })); -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); const createMockEntity = (annotations: {}) => { return { diff --git a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts index 9a9c9b4542..e43b303dd4 100644 --- a/plugins/techdocs-node/src/stages/publish/awsS3.test.ts +++ b/plugins/techdocs-node/src/stages/publish/awsS3.test.ts @@ -24,7 +24,6 @@ import { S3Client, UploadPartCommand, } from '@aws-sdk/client-s3'; -import { getVoidLogger } from '@backstage/backend-common'; import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { @@ -39,7 +38,11 @@ import path from 'path'; import fs from 'fs-extra'; import { AwsS3Publish } from './awsS3'; import { Readable } from 'stream'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; const env = process.env; let s3Mock: AwsClientStub; @@ -87,7 +90,7 @@ class ErrorReadable extends Readable { } } -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); const loggerInfoSpy = jest.spyOn(logger, 'info'); const loggerErrorSpy = jest.spyOn(logger, 'error'); diff --git a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts index 50e0863f3c..6cbfa76c81 100644 --- a/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/azureBlobStorage.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import express from 'express'; @@ -27,7 +27,10 @@ import { BlobUploadCommonResponse, ContainerGetPropertiesResponse, } from '@azure/storage-blob'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; const mockDir = createMockDirectory(); @@ -221,7 +224,7 @@ const getEntityRootDir = (entity: Entity) => { return mockDir.resolve(namespace || DEFAULT_NAMESPACE, kind, name); }; -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); jest.spyOn(logger, 'error').mockReturnValue(logger); const createPublisherFromConfig = ({ diff --git a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts index 17bfb3e3c4..0c94a69d6a 100644 --- a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import express from 'express'; @@ -23,7 +23,10 @@ import path from 'path'; import fs from 'fs-extra'; import { Readable } from 'stream'; import { GoogleGCSPublish } from './googleStorage'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; const mockDir = createMockDirectory(); @@ -136,7 +139,7 @@ const getEntityRootDir = (entity: Entity) => { return mockDir.resolve(namespace || DEFAULT_NAMESPACE, kind, name); }; -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); jest.spyOn(logger, 'info').mockReturnValue(logger); jest.spyOn(logger, 'error').mockReturnValue(logger); diff --git a/plugins/techdocs-node/src/stages/publish/local.test.ts b/plugins/techdocs-node/src/stages/publish/local.test.ts index 89a38c7ed2..e5e71c51ec 100644 --- a/plugins/techdocs-node/src/stages/publish/local.test.ts +++ b/plugins/techdocs-node/src/stages/publish/local.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { - getVoidLogger, + loggerToWinstonLogger, PluginEndpointDiscovery, } from '@backstage/backend-common'; import { overridePackagePathResolution } from '@backstage/backend-plugin-api/testUtils'; @@ -24,7 +24,10 @@ import request from 'supertest'; import * as os from 'os'; import { LocalPublish } from './local'; import path from 'path'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; const createMockEntity = (annotations = {}, lowerCase = false) => { return { @@ -53,7 +56,7 @@ overridePackagePathResolution({ }, }); -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); describe('local publisher', () => { const mockDir = createMockDirectory(); diff --git a/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts b/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts index 93fe54ac27..aa0e5f0afa 100644 --- a/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts +++ b/plugins/techdocs-node/src/stages/publish/openStackSwift.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { Entity, CompoundEntityRef, @@ -28,7 +28,10 @@ import path from 'path'; import { OpenStackSwiftPublish } from './openStackSwift'; import { PublisherBase, TechDocsMetadata } from './types'; import { Stream, Readable } from 'stream'; -import { createMockDirectory } from '@backstage/backend-test-utils'; +import { + createMockDirectory, + mockServices, +} from '@backstage/backend-test-utils'; const mockDir = createMockDirectory(); @@ -169,7 +172,7 @@ const getPosixEntityRootDir = (entity: Entity) => { ); }; -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); let publisher: PublisherBase; diff --git a/plugins/techdocs-node/src/stages/publish/publish.test.ts b/plugins/techdocs-node/src/stages/publish/publish.test.ts index ab300412a4..4059eccfeb 100644 --- a/plugins/techdocs-node/src/stages/publish/publish.test.ts +++ b/plugins/techdocs-node/src/stages/publish/publish.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import { - getVoidLogger, PluginEndpointDiscovery, + loggerToWinstonLogger, } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Publisher } from './publish'; @@ -24,8 +24,9 @@ import { GoogleGCSPublish } from './googleStorage'; import { AwsS3Publish } from './awsS3'; import { AzureBlobStoragePublish } from './azureBlobStorage'; import { OpenStackSwiftPublish } from './openStackSwift'; +import { mockServices } from '@backstage/backend-test-utils'; -const logger = getVoidLogger(); +const logger = loggerToWinstonLogger(mockServices.logger.mock()); const discovery: jest.Mocked = { getBaseUrl: jest.fn().mockResolvedValueOnce('http://localhost:7007'), getExternalBaseUrl: jest.fn(), diff --git a/yarn.lock b/yarn.lock index f10de89d74..8ff4221b13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5770,6 +5770,7 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" languageName: unknown linkType: soft @@ -6892,6 +6893,7 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/integration-aws-node": "workspace:^" @@ -6995,6 +6997,7 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" @@ -7125,6 +7128,7 @@ __metadata: "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/plugin-auth-backend": "workspace:^" @@ -9481,6 +9485,7 @@ __metadata: dependencies: "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" From 17791889eaec70f7228dda607b13f839ccfdf059 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 16 May 2024 09:27:14 +0200 Subject: [PATCH 439/567] docs: add changeset files Signed-off-by: Camila Belo --- .changeset/eighty-kings-dress.md | 5 +++++ .changeset/wet-crabs-guess.md | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/eighty-kings-dress.md create mode 100644 .changeset/wet-crabs-guess.md diff --git a/.changeset/eighty-kings-dress.md b/.changeset/eighty-kings-dress.md new file mode 100644 index 0000000000..f83c2ab2b2 --- /dev/null +++ b/.changeset/eighty-kings-dress.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +In preparation to the new backend system stable release, the `isDatabaseConnectionError` helper have been moved to the `backend-plugin-api` package and deprecated from `backend-common`. diff --git a/.changeset/wet-crabs-guess.md b/.changeset/wet-crabs-guess.md new file mode 100644 index 0000000000..e15e56e77d --- /dev/null +++ b/.changeset/wet-crabs-guess.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': patch +'@backstage/plugin-catalog-backend': patch +--- + +Start using the `isDatabaseConflictError` helper from the `backend-plugin-api` package in order to avoid dependency with the soon to deprecate `backend-common` package. From abc983847fd81540bacad5962703424d7c8fe97d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 24 Apr 2024 15:15:10 +0200 Subject: [PATCH 440/567] add the scheduler definitions to backend-plugin-api/scheduler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- packages/backend-app-api/api-report.md | 2 +- packages/backend-common/api-report.md | 2 +- .../api-report-scheduler.md | 79 ++++ packages/backend-plugin-api/api-report.md | 8 +- packages/backend-plugin-api/package.json | 7 +- .../SchedulerService.ts => deprecated.ts} | 11 +- .../src/entrypoints/scheduler/index.ts | 26 ++ ...adTaskScheduleDefinitionFromConfig.test.ts | 132 +++++++ .../readTaskScheduleDefinitionFromConfig.ts | 78 ++++ .../src/entrypoints/scheduler/types.ts | 343 ++++++++++++++++++ packages/backend-plugin-api/src/index.ts | 1 + .../src/services/definitions/coreServices.ts | 2 +- .../src/services/definitions/index.ts | 1 - packages/backend-test-utils/api-report.md | 2 +- yarn.lock | 1 + 15 files changed, 681 insertions(+), 14 deletions(-) create mode 100644 packages/backend-plugin-api/api-report-scheduler.md rename packages/backend-plugin-api/src/{services/definitions/SchedulerService.ts => deprecated.ts} (66%) create mode 100644 packages/backend-plugin-api/src/entrypoints/scheduler/index.ts create mode 100644 packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts create mode 100644 packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts create mode 100644 packages/backend-plugin-api/src/entrypoints/scheduler/types.ts diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 5720c1c60f..cb0469d9d7 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -36,7 +36,7 @@ import { RootConfigService } from '@backstage/backend-plugin-api'; import { RootHttpRouterService } from '@backstage/backend-plugin-api'; import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerService } from '@backstage/backend-plugin-api/scheduler'; import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api'; diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 42df32dc1d..f9f25c3b81 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -59,7 +59,7 @@ import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-p import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; -import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerService } from '@backstage/backend-plugin-api/scheduler'; import { SearchOptions } from '@backstage/backend-plugin-api'; import { SearchResponse } from '@backstage/backend-plugin-api'; import { SearchResponseFile } from '@backstage/backend-plugin-api'; diff --git a/packages/backend-plugin-api/api-report-scheduler.md b/packages/backend-plugin-api/api-report-scheduler.md new file mode 100644 index 0000000000..480c716025 --- /dev/null +++ b/packages/backend-plugin-api/api-report-scheduler.md @@ -0,0 +1,79 @@ +## API Report File for "@backstage/backend-plugin-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { Config } from '@backstage/config'; +import { Duration } from 'luxon'; +import { HumanDuration } from '@backstage/types'; +import { JsonObject } from '@backstage/types'; + +// @public +export function readTaskScheduleDefinitionFromConfig( + config: Config, +): TaskScheduleDefinition; + +// @public +export interface SchedulerService { + createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner; + getScheduledTasks(): Promise; + scheduleTask( + task: TaskScheduleDefinition & TaskInvocationDefinition, + ): Promise; + triggerTask(id: string): Promise; +} + +// @public +export type TaskDescriptor = { + id: string; + scope: 'global' | 'local'; + settings: { + version: number; + } & JsonObject; +}; + +// @public +export type TaskFunction = + | ((abortSignal: AbortSignal) => void | Promise) + | (() => void | Promise); + +// @public +export interface TaskInvocationDefinition { + fn: TaskFunction; + id: string; + signal?: AbortSignal; +} + +// @public +export interface TaskRunner { + run(task: TaskInvocationDefinition): Promise; +} + +// @public +export interface TaskScheduleDefinition { + frequency: + | { + cron: string; + } + | Duration + | HumanDuration; + initialDelay?: Duration | HumanDuration; + scope?: 'global' | 'local'; + timeout: Duration | HumanDuration; +} + +// @public +export interface TaskScheduleDefinitionConfig { + frequency: + | { + cron: string; + } + | string + | HumanDuration; + initialDelay?: string | HumanDuration; + scope?: 'global' | 'local'; + timeout: string | HumanDuration; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 932ccc74ab..4e03da15b3 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -15,12 +15,12 @@ import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { QueryPermissionResponse } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; import { Request as Request_2 } from 'express'; import { Response as Response_2 } from 'express'; +import { SchedulerService as SchedulerService_2 } from '@backstage/backend-plugin-api/scheduler'; // @public (undocumented) export interface AuthService { @@ -202,7 +202,7 @@ export namespace coreServices { const rootHttpRouter: ServiceRef; const rootLifecycle: ServiceRef; const rootLogger: ServiceRef; - const scheduler: ServiceRef; + const scheduler: ServiceRef; const tokenManager: ServiceRef; const urlReader: ServiceRef; const identity: ServiceRef; @@ -536,8 +536,8 @@ export interface RootServiceFactoryConfig< service: ServiceRef; } -// @public (undocumented) -export interface SchedulerService extends PluginTaskScheduler {} +// @public @deprecated (undocumented) +export type SchedulerService = SchedulerService_2; // @public export type SearchOptions = { diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 3199c97bda..3ffbb5b967 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -20,6 +20,7 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", + "./scheduler": "./src/entrypoints/scheduler/index.ts", "./alpha": "./src/alpha.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" @@ -28,6 +29,9 @@ "types": "src/index.ts", "typesVersions": { "*": { + "scheduler": [ + "src/entrypoints/scheduler/index.ts" + ], "alpha": [ "src/alpha.ts" ], @@ -61,7 +65,8 @@ "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", - "knex": "^3.0.0" + "knex": "^3.0.0", + "luxon": "^3.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts b/packages/backend-plugin-api/src/deprecated.ts similarity index 66% rename from packages/backend-plugin-api/src/services/definitions/SchedulerService.ts rename to packages/backend-plugin-api/src/deprecated.ts index 07c436bd6b..3cf6b0993e 100644 --- a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts +++ b/packages/backend-plugin-api/src/deprecated.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Backstage Authors + * 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. @@ -14,7 +14,10 @@ * limitations under the License. */ -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { type SchedulerService as SchedulerService_ } from '@backstage/backend-plugin-api/scheduler'; -/** @public */ -export interface SchedulerService extends PluginTaskScheduler {} +/** + * @public + * @deprecated import from `@backstage/backend-plugin-api/scheduler` instead + */ +export type SchedulerService = SchedulerService_; diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/index.ts b/packages/backend-plugin-api/src/entrypoints/scheduler/index.ts new file mode 100644 index 0000000000..8c7dbf65f6 --- /dev/null +++ b/packages/backend-plugin-api/src/entrypoints/scheduler/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; +export type { + SchedulerService, + TaskDescriptor, + TaskFunction, + TaskInvocationDefinition, + TaskRunner, + TaskScheduleDefinition, + TaskScheduleDefinitionConfig, +} from './types'; diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts new file mode 100644 index 0000000000..c52d59b016 --- /dev/null +++ b/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts @@ -0,0 +1,132 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { HumanDuration } from '@backstage/types'; +import { Duration } from 'luxon'; +import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; + +describe('readTaskScheduleDefinitionFromConfig', () => { + it('all valid values', () => { + const config = new ConfigReader({ + frequency: { + cron: '0 30 * * * *', + }, + timeout: 'PT3M', + initialDelay: { + minutes: 20, + }, + scope: 'global', + }); + + const result = readTaskScheduleDefinitionFromConfig(config); + + expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); + expect(result.timeout).toEqual(Duration.fromISO('PT3M')); + expect((result.initialDelay as HumanDuration).minutes).toEqual(20); + expect(result.scope).toBe('global'); + }); + + it('all valid required values', () => { + const config = new ConfigReader({ + frequency: { + cron: '0 30 * * * *', + }, + timeout: 'PT3M', + }); + + const result = readTaskScheduleDefinitionFromConfig(config); + + expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); + expect(result.timeout).toEqual(Duration.fromISO('PT3M')); + expect(result.initialDelay).toBeUndefined(); + expect(result.scope).toBeUndefined(); + }); + + it('fail without required frequency', () => { + const config = new ConfigReader({ + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Missing required config value at 'frequency'", + ); + }); + + it('fail without required timeout', () => { + const config = new ConfigReader({ + frequency: 'PT30M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Missing required config value at 'timeout'", + ); + }); + + it('invalid frequency key', () => { + const config = new ConfigReader({ + frequency: { + invalid: 'value', + }, + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Failed to read duration from config at 'frequency', Error: Needs one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'", + ); + }); + + it('invalid frequency value', () => { + const config = new ConfigReader({ + frequency: { + minutes: 'value', + }, + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Failed to read duration from config, Error: Unable to convert config value for key 'frequency.minutes' in 'mock-config' to a number", + ); + }); + + it('frequency value with additional invalid prop', () => { + const config = new ConfigReader({ + frequency: { + minutes: 20, + invalid: 'value', + }, + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Failed to read duration from config at 'frequency', Error: Unknown property 'invalid'; expected one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'", + ); + }); + + it('invalid scope value', () => { + const config = new ConfigReader({ + frequency: { + years: 2, + }, + timeout: 'PT3M', + scope: 'invalid', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + 'Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: invalid', + ); + }); +}); diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts new file mode 100644 index 0000000000..8053936263 --- /dev/null +++ b/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config, readDurationFromConfig } from '@backstage/config'; +import { HumanDuration } from '@backstage/types'; +import { TaskScheduleDefinition } from './types'; +import { Duration } from 'luxon'; + +function readDuration(config: Config, key: string): Duration | HumanDuration { + if (typeof config.get(key) === 'string') { + const value = config.getString(key); + const duration = Duration.fromISO(value); + if (!duration.isValid) { + throw new Error(`Invalid duration: ${value}`); + } + return duration; + } + + return readDurationFromConfig(config, { key }); +} + +function readCronOrDuration( + config: Config, + key: string, +): { cron: string } | Duration | HumanDuration { + const value = config.get(key); + if (typeof value === 'object' && (value as { cron?: string }).cron) { + return value as { cron: string }; + } + + return readDuration(config, key); +} + +/** + * Reads a TaskScheduleDefinition from a Config. + * Expects the config not to be the root config, + * but the config for the definition. + * + * @param config - config for a TaskScheduleDefinition. + * @public + */ +export function readTaskScheduleDefinitionFromConfig( + config: Config, +): TaskScheduleDefinition { + const frequency = readCronOrDuration(config, 'frequency'); + const timeout = readDuration(config, 'timeout'); + + const initialDelay = config.has('initialDelay') + ? readDuration(config, 'initialDelay') + : undefined; + + const scope = config.getOptionalString('scope'); + if (scope && !['global', 'local'].includes(scope)) { + throw new Error( + `Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: ${scope}`, + ); + } + + return { + frequency, + timeout, + initialDelay, + scope: scope as 'global' | 'local' | undefined, + }; +} diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/types.ts b/packages/backend-plugin-api/src/entrypoints/scheduler/types.ts new file mode 100644 index 0000000000..5b14e52fcb --- /dev/null +++ b/packages/backend-plugin-api/src/entrypoints/scheduler/types.ts @@ -0,0 +1,343 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HumanDuration, JsonObject } from '@backstage/types'; +import { Duration } from 'luxon'; + +/** + * A function that can be called as a scheduled task. + * + * It may optionally accept an abort signal argument. When the signal triggers, + * processing should abort and return as quickly as possible. + * + * @public + */ +export type TaskFunction = + | ((abortSignal: AbortSignal) => void | Promise) + | (() => void | Promise); + +/** + * A semi-opaque type to describe an actively scheduled task. + * + * @public + */ +export type TaskDescriptor = { + /** + * The unique identifier of the task. + */ + id: string; + /** + * The scope of the task. + */ + scope: 'global' | 'local'; + /** + * The settings that control the task flow. This is a semi-opaque structure + * that is mainly there for debugging purposes. Do not make any assumptions + * about the contents of this field. + */ + settings: { version: number } & JsonObject; +}; + +/** + * Options that control the scheduling of a task. + * + * @public + */ +export interface TaskScheduleDefinition { + /** + * How often you want the task to run. The system does its best to avoid + * overlapping invocations. + * + * @remarks + * + * This is the best effort value; under some circumstances there can be + * deviations. For example, if the task runtime is longer than the frequency + * and the timeout has not been given or not been exceeded yet, the next + * invocation of this task will be delayed until after the previous one + * finishes. + * + * This is a required field. + */ + frequency: + | { + /** + * A crontab style string. + * + * @remarks + * + * Overview: + * + * ``` + * ┌────────────── second (optional) + * │ ┌──────────── minute + * │ │ ┌────────── hour + * │ │ │ ┌──────── day of month + * │ │ │ │ ┌────── month + * │ │ │ │ │ ┌──── day of week + * │ │ │ │ │ │ + * │ │ │ │ │ │ + * * * * * * * + * ``` + */ + cron: string; + } + | Duration + | HumanDuration; + + /** + * The maximum amount of time that a single task invocation can take, before + * it's considered timed out and gets "released" such that a new invocation + * is permitted to take place (possibly, then, on a different worker). + */ + timeout: Duration | HumanDuration; + + /** + * The amount of time that should pass before the first invocation happens. + * + * @remarks + * + * This can be useful in cold start scenarios to stagger or delay some heavy + * compute jobs. If no value is given for this field then the first invocation + * will happen as soon as possible according to the cadence. + * + * NOTE: This is a per-worker delay. If you have a cluster of workers all + * collaborating on a task that has its `scope` field set to `'global'`, then + * you may still see the task being processed by other long-lived workers, + * while any given single worker is in its initial sleep delay time e.g. after + * a deployment. Therefore, this parameter is not useful for "globally" pausing + * work; its main intended use is for individual machines to get a chance to + * reach some equilibrium at startup before triggering heavy batch workloads. + */ + initialDelay?: Duration | HumanDuration; + + /** + * Sets the scope of concurrency control / locking to apply for invocations of + * this task. + * + * @remarks + * + * When the scope is set to the default value `'global'`, the scheduler will + * attempt to ensure that only one worker machine runs the task at a time, + * according to the given cadence. This means that as the number of worker + * hosts increases, the invocation frequency of this task will not go up. + * Instead, the load is spread randomly across hosts. This setting is useful + * for tasks that access shared resources, for example catalog ingestion tasks + * where you do not want many machines to repeatedly import the same data and + * trample over each other. + * + * When the scope is set to `'local'`, there is no concurrency control across + * hosts. Each host runs the task according to the given cadence similarly to + * `setInterval`, but the runtime ensures that there are no overlapping runs. + * + * @defaultValue 'global' + */ + scope?: 'global' | 'local'; +} + +/** + * Config options for {@link TaskScheduleDefinition} + * that control the scheduling of a task. + * + * @public + */ +export interface TaskScheduleDefinitionConfig { + /** + * How often you want the task to run. The system does its best to avoid + * overlapping invocations. + * + * @remarks + * + * This is the best effort value; under some circumstances there can be + * deviations. For example, if the task runtime is longer than the frequency + * and the timeout has not been given or not been exceeded yet, the next + * invocation of this task will be delayed until after the previous one + * finishes. + * + * This is a required field. + */ + frequency: + | { + /** + * A crontab style string. + * + * @remarks + * + * Overview: + * + * ``` + * ┌────────────── second (optional) + * │ ┌──────────── minute + * │ │ ┌────────── hour + * │ │ │ ┌──────── day of month + * │ │ │ │ ┌────── month + * │ │ │ │ │ ┌──── day of week + * │ │ │ │ │ │ + * │ │ │ │ │ │ + * * * * * * * + * ``` + */ + cron: string; + } + | string + | HumanDuration; + + /** + * The maximum amount of time that a single task invocation can take, before + * it's considered timed out and gets "released" such that a new invocation + * is permitted to take place (possibly, then, on a different worker). + */ + timeout: string | HumanDuration; + + /** + * The amount of time that should pass before the first invocation happens. + * + * @remarks + * + * This can be useful in cold start scenarios to stagger or delay some heavy + * compute jobs. If no value is given for this field then the first invocation + * will happen as soon as possible according to the cadence. + * + * NOTE: This is a per-worker delay. If you have a cluster of workers all + * collaborating on a task that has its `scope` field set to `'global'`, then + * you may still see the task being processed by other long-lived workers, + * while any given single worker is in its initial sleep delay time e.g. after + * a deployment. Therefore, this parameter is not useful for "globally" pausing + * work; its main intended use is for individual machines to get a chance to + * reach some equilibrium at startup before triggering heavy batch workloads. + */ + initialDelay?: string | HumanDuration; + + /** + * Sets the scope of concurrency control / locking to apply for invocations of + * this task. + * + * @remarks + * + * When the scope is set to the default value `'global'`, the scheduler will + * attempt to ensure that only one worker machine runs the task at a time, + * according to the given cadence. This means that as the number of worker + * hosts increases, the invocation frequency of this task will not go up. + * Instead, the load is spread randomly across hosts. This setting is useful + * for tasks that access shared resources, for example catalog ingestion tasks + * where you do not want many machines to repeatedly import the same data and + * trample over each other. + * + * When the scope is set to `'local'`, there is no concurrency control across + * hosts. Each host runs the task according to the given cadence similarly to + * `setInterval`, but the runtime ensures that there are no overlapping runs. + * + * @defaultValue 'global' + */ + scope?: 'global' | 'local'; +} + +/** + * Options that apply to the invocation of a given task. + * + * @public + */ +export interface TaskInvocationDefinition { + /** + * A unique ID (within the scope of the plugin) for the task. + */ + id: string; + + /** + * The actual task function to be invoked regularly. + */ + fn: TaskFunction; + + /** + * An abort signal that, when triggered, will stop the recurring execution of + * the task. + */ + signal?: AbortSignal; +} + +/** + * A previously prepared task schedule, ready to be invoked. + * + * @public + */ +export interface TaskRunner { + /** + * Takes the schedule and executes an actual task using it. + * + * @param task - The actual runtime properties of the task + */ + run(task: TaskInvocationDefinition): Promise; +} + +/** + * Deals with the scheduling of distributed tasks, for a given plugin. + * + * @public + */ +export interface SchedulerService { + /** + * Manually triggers a task by ID. + * + * If the task doesn't exist, a NotFoundError is thrown. If the task is + * currently running, a ConflictError is thrown. + * + * @param id - The task ID + */ + triggerTask(id: string): Promise; + + /** + * Schedules a task function for recurring runs. + * + * @remarks + * + * The `scope` task field controls whether to use coordinated exclusive + * invocation across workers, or to just coordinate within the current worker. + * + * This convenience method performs both the scheduling and invocation in one + * go. + * + * @param task - The task definition + */ + scheduleTask( + task: TaskScheduleDefinition & TaskInvocationDefinition, + ): Promise; + + /** + * Creates a scheduled but dormant recurring task, ready to be launched at a + * later time. + * + * @remarks + * + * This method is useful for pre-creating a schedule in outer code to be + * passed into an inner implementation, such that the outer code controls + * scheduling while inner code controls implementation. + * + * @param schedule - The task schedule + */ + createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner; + + /** + * Returns all scheduled tasks registered to this scheduler. + * + * @remarks + * + * This method is useful for triggering tasks manually using the triggerTask + * functionality. Note that the returned tasks contain only tasks that have + * been initialized in this instance of the scheduler. + * + * @returns Scheduled tasks + */ + getScheduledTasks(): Promise; +} diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts index c2e2a13a89..342b33dd2e 100644 --- a/packages/backend-plugin-api/src/index.ts +++ b/packages/backend-plugin-api/src/index.ts @@ -24,3 +24,4 @@ export * from './services'; export type { BackendFeature } from './types'; export * from './paths'; export * from './wiring'; +export * from './deprecated'; diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index c760afc7b4..09f313af57 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -165,7 +165,7 @@ export namespace coreServices { * @public */ export const scheduler = createServiceRef< - import('./SchedulerService').SchedulerService + import('@backstage/backend-plugin-api/scheduler').SchedulerService >({ id: 'core.scheduler' }); /** diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 8a5176379f..bd436cf899 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -52,7 +52,6 @@ export type { PluginMetadataService } from './PluginMetadataService'; export type { RootHttpRouterService } from './RootHttpRouterService'; export type { RootLifecycleService } from './RootLifecycleService'; export type { RootLoggerService } from './RootLoggerService'; -export type { SchedulerService } from './SchedulerService'; export type { TokenManagerService } from './TokenManagerService'; export type { ReadTreeOptions, diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 4f6eb47243..6e9cdc9459 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -34,7 +34,7 @@ import { RootHttpRouterFactoryOptions } from '@backstage/backend-app-api'; import { RootHttpRouterService } from '@backstage/backend-plugin-api'; import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerService } from '@backstage/backend-plugin-api/scheduler'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; diff --git a/yarn.lock b/yarn.lock index 728bac0c68..3bdc15bbb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3518,6 +3518,7 @@ __metadata: "@types/express": ^4.17.6 express: ^4.17.1 knex: ^3.0.0 + luxon: ^3.0.0 languageName: unknown linkType: soft From 736bc3c24375938c21e266ec67efeeb964feef7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 25 Apr 2024 11:54:18 +0200 Subject: [PATCH 441/567] deprecate everything in backend-tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/calm-cars-serve.md | 5 +++++ packages/backend-tasks/api-report.md | 18 +++++++++--------- packages/backend-tasks/src/deprecated.ts | 2 ++ packages/backend-tasks/src/index.ts | 1 - .../backend-tasks/src/tasks/TaskScheduler.ts | 1 + .../readTaskScheduleDefinitionFromConfig.ts | 1 + packages/backend-tasks/src/tasks/types.ts | 7 +++++++ 7 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 .changeset/calm-cars-serve.md diff --git a/.changeset/calm-cars-serve.md b/.changeset/calm-cars-serve.md new file mode 100644 index 0000000000..5197edda5c --- /dev/null +++ b/.changeset/calm-cars-serve.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Marked all exports as deprecated and pointed at `@backstage/backend-plugin-api/scheduler` and `@backstage/backend-defaults/scheduler` diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index 00ef28c9ce..556cf2806c 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -14,7 +14,7 @@ import { PluginDatabaseManager } from '@backstage/backend-common'; // @public @deprecated export type HumanDuration = HumanDuration_2; -// @public +// @public @deprecated export interface PluginTaskScheduler { createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner; getScheduledTasks(): Promise; @@ -24,12 +24,12 @@ export interface PluginTaskScheduler { triggerTask(id: string): Promise; } -// @public +// @public @deprecated export function readTaskScheduleDefinitionFromConfig( config: Config, ): TaskScheduleDefinition; -// @public +// @public @deprecated export type TaskDescriptor = { id: string; scope: 'global' | 'local'; @@ -38,24 +38,24 @@ export type TaskDescriptor = { } & JsonObject; }; -// @public +// @public @deprecated export type TaskFunction = | ((abortSignal: AbortSignal) => void | Promise) | (() => void | Promise); -// @public +// @public @deprecated export interface TaskInvocationDefinition { fn: TaskFunction; id: string; signal?: AbortSignal; } -// @public +// @public @deprecated export interface TaskRunner { run(task: TaskInvocationDefinition): Promise; } -// @public +// @public @deprecated export interface TaskScheduleDefinition { frequency: | { @@ -68,7 +68,7 @@ export interface TaskScheduleDefinition { timeout: Duration | HumanDuration_2; } -// @public +// @public @deprecated export interface TaskScheduleDefinitionConfig { frequency: | { @@ -81,7 +81,7 @@ export interface TaskScheduleDefinitionConfig { timeout: string | HumanDuration_2; } -// @public +// @public @deprecated export class TaskScheduler { constructor( databaseManager: LegacyRootDatabaseService, diff --git a/packages/backend-tasks/src/deprecated.ts b/packages/backend-tasks/src/deprecated.ts index 4d86cb1700..629e5f6d4d 100644 --- a/packages/backend-tasks/src/deprecated.ts +++ b/packages/backend-tasks/src/deprecated.ts @@ -23,3 +23,5 @@ import { HumanDuration as TypesHumanDuration } from '@backstage/types'; * @deprecated Import from `@backstage/types` instead */ export type HumanDuration = TypesHumanDuration; + +export * from './tasks'; diff --git a/packages/backend-tasks/src/index.ts b/packages/backend-tasks/src/index.ts index d35917056c..0aae81b143 100644 --- a/packages/backend-tasks/src/index.ts +++ b/packages/backend-tasks/src/index.ts @@ -20,5 +20,4 @@ * @packageDocumentation */ -export * from './tasks'; export * from './deprecated'; diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts index 672c52cc10..ac9039adcd 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts @@ -33,6 +33,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; * Deals with the scheduling of distributed tasks. * * @public + * @deprecated Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead. The new default implementation of this service lives in `@backstage/backend-defaults/scheduler`. */ export class TaskScheduler { static fromConfig( diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts index 8053936263..2e761f2f94 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts @@ -51,6 +51,7 @@ function readCronOrDuration( * * @param config - config for a TaskScheduleDefinition. * @public + * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead */ export function readTaskScheduleDefinitionFromConfig( config: Config, diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index 14339dda0e..3d49e19cc1 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -26,6 +26,7 @@ import { z } from 'zod'; * processing should abort and return as quickly as possible. * * @public + * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead */ export type TaskFunction = | ((abortSignal: AbortSignal) => void | Promise) @@ -35,6 +36,7 @@ export type TaskFunction = * A semi-opaque type to describe an actively scheduled task. * * @public + * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead */ export type TaskDescriptor = { /** @@ -57,6 +59,7 @@ export type TaskDescriptor = { * Options that control the scheduling of a task. * * @public + * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead */ export interface TaskScheduleDefinition { /** @@ -154,6 +157,7 @@ export interface TaskScheduleDefinition { * that control the scheduling of a task. * * @public + * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead */ export interface TaskScheduleDefinitionConfig { /** @@ -250,6 +254,7 @@ export interface TaskScheduleDefinitionConfig { * Options that apply to the invocation of a given task. * * @public + * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead */ export interface TaskInvocationDefinition { /** @@ -273,6 +278,7 @@ export interface TaskInvocationDefinition { * A previously prepared task schedule, ready to be invoked. * * @public + * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead */ export interface TaskRunner { /** @@ -287,6 +293,7 @@ export interface TaskRunner { * Deals with the scheduling of distributed tasks, for a given plugin. * * @public + * @deprecated Please use `SchedulerService` from `@backstage/backend-plugin-api/scheduler` instead */ export interface PluginTaskScheduler { /** From 906705b77be1be59177fac41e5b79b9b6b18d5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 25 Apr 2024 16:06:57 +0200 Subject: [PATCH 442/567] arrange in backend-plugin-api and backend-defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/calm-cars-serve.md | 2 +- packages/backend-app-api/api-report.md | 4 +- .../services/implementations}/deprecated.ts | 8 +- .../src/services/implementations/index.ts | 3 +- .../scheduler/schedulerServiceFactory.ts | 5 +- packages/backend-common/api-report.md | 2 +- .../backend-defaults/api-report-scheduler.md | 16 + .../scheduler/20210928160613_init.js | 64 +++ packages/backend-defaults/package.json | 33 +- .../backend-defaults/src/CreateBackend.ts | 2 +- .../scheduler/database/migrateBackendTasks.ts | 31 ++ .../entrypoints/scheduler/database/tables.ts | 27 + .../src/entrypoints/scheduler/index.ts | 11 +- .../scheduler/lib/LocalTaskWorker.test.ts | 109 ++++ .../scheduler/lib/LocalTaskWorker.ts | 155 ++++++ .../lib/PluginTaskSchedulerImpl.test.ts | 349 ++++++++++++ .../scheduler/lib/PluginTaskSchedulerImpl.ts | 169 ++++++ .../lib/PluginTaskSchedulerJanitor.test.ts | 96 ++++ .../lib/PluginTaskSchedulerJanitor.ts | 96 ++++ .../scheduler/lib/TaskScheduler.test.ts | 84 +++ .../scheduler/lib/TaskScheduler.ts | 98 ++++ .../scheduler/lib/TaskWorker.test.ts | 507 ++++++++++++++++++ .../entrypoints/scheduler/lib/TaskWorker.ts | 373 +++++++++++++ .../__testUtils__/createTestScopedSignal.ts | 28 + .../src/entrypoints/scheduler/lib/types.ts | 158 ++++++ .../entrypoints/scheduler/lib/util.test.ts | 113 ++++ .../src/entrypoints/scheduler/lib/util.ts | 111 ++++ .../scheduler/schedulerServiceFactory.test.ts | 46 ++ .../scheduler/schedulerServiceFactory.ts | 42 ++ packages/backend-defaults/src/setupTests.ts | 25 + .../api-report-scheduler.md | 79 --- packages/backend-plugin-api/api-report.md | 76 ++- packages/backend-plugin-api/package.json | 4 - .../readTaskScheduleDefinitionFromConfig.ts | 78 --- packages/backend-plugin-api/src/index.ts | 1 - .../definitions/SchedulerService.test.ts} | 36 +- .../definitions/SchedulerService.ts} | 85 ++- .../src/services/definitions/coreServices.ts | 2 +- .../src/services/definitions/index.ts | 10 + .../backend-tasks/src/tasks/TaskScheduler.ts | 2 +- .../readTaskScheduleDefinitionFromConfig.ts | 2 +- packages/backend-tasks/src/tasks/types.ts | 14 +- packages/backend-test-utils/api-report.md | 2 +- yarn.lock | 10 + 44 files changed, 2938 insertions(+), 230 deletions(-) rename packages/{backend-plugin-api/src => backend-app-api/src/services/implementations}/deprecated.ts (70%) create mode 100644 packages/backend-defaults/api-report-scheduler.md create mode 100644 packages/backend-defaults/migrations/scheduler/20210928160613_init.js create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts rename packages/{backend-plugin-api => backend-defaults}/src/entrypoints/scheduler/index.ts (68%) create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/__testUtils__/createTestScopedSignal.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/util.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts create mode 100644 packages/backend-defaults/src/setupTests.ts delete mode 100644 packages/backend-plugin-api/api-report-scheduler.md delete mode 100644 packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts rename packages/backend-plugin-api/src/{entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts => services/definitions/SchedulerService.test.ts} (76%) rename packages/backend-plugin-api/src/{entrypoints/scheduler/types.ts => services/definitions/SchedulerService.ts} (81%) diff --git a/.changeset/calm-cars-serve.md b/.changeset/calm-cars-serve.md index 5197edda5c..5df2a18cd4 100644 --- a/.changeset/calm-cars-serve.md +++ b/.changeset/calm-cars-serve.md @@ -2,4 +2,4 @@ '@backstage/backend-tasks': patch --- -Marked all exports as deprecated and pointed at `@backstage/backend-plugin-api/scheduler` and `@backstage/backend-defaults/scheduler` +Marked all exports as deprecated and pointed at `@backstage/backend-plugin-api` and `@backstage/backend-defaults` diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index cb0469d9d7..789873e53c 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -36,7 +36,7 @@ import { RootConfigService } from '@backstage/backend-plugin-api'; import { RootHttpRouterService } from '@backstage/backend-plugin-api'; import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { SchedulerService } from '@backstage/backend-plugin-api/scheduler'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api'; @@ -326,7 +326,7 @@ export const rootLoggerServiceFactory: () => ServiceFactory< 'root' >; -// @public (undocumented) +// @public @deprecated (undocumented) export const schedulerServiceFactory: () => ServiceFactory< SchedulerService, 'plugin' diff --git a/packages/backend-plugin-api/src/deprecated.ts b/packages/backend-app-api/src/services/implementations/deprecated.ts similarity index 70% rename from packages/backend-plugin-api/src/deprecated.ts rename to packages/backend-app-api/src/services/implementations/deprecated.ts index 3cf6b0993e..6f4f76e679 100644 --- a/packages/backend-plugin-api/src/deprecated.ts +++ b/packages/backend-app-api/src/services/implementations/deprecated.ts @@ -14,10 +14,4 @@ * limitations under the License. */ -import { type SchedulerService as SchedulerService_ } from '@backstage/backend-plugin-api/scheduler'; - -/** - * @public - * @deprecated import from `@backstage/backend-plugin-api/scheduler` instead - */ -export type SchedulerService = SchedulerService_; +export * from './scheduler'; diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-app-api/src/services/implementations/index.ts index a1114ab3e3..e6656801a9 100644 --- a/packages/backend-app-api/src/services/implementations/index.ts +++ b/packages/backend-app-api/src/services/implementations/index.ts @@ -28,7 +28,8 @@ export * from './permissions'; export * from './rootHttpRouter'; export * from './rootLifecycle'; export * from './rootLogger'; -export * from './scheduler'; export * from './tokenManager'; export * from './urlReader'; export * from './userInfo'; + +export * from './deprecated'; diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts index dc9b3a9864..b4370761d9 100644 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts @@ -20,7 +20,10 @@ import { } from '@backstage/backend-plugin-api'; import { TaskScheduler } from '@backstage/backend-tasks'; -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/scheduler` instead. + */ export const schedulerServiceFactory = createServiceFactory({ service: coreServices.scheduler, deps: { diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index f9f25c3b81..42df32dc1d 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -59,7 +59,7 @@ import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-p import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; -import { SchedulerService } from '@backstage/backend-plugin-api/scheduler'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { SearchOptions } from '@backstage/backend-plugin-api'; import { SearchResponse } from '@backstage/backend-plugin-api'; import { SearchResponseFile } from '@backstage/backend-plugin-api'; diff --git a/packages/backend-defaults/api-report-scheduler.md b/packages/backend-defaults/api-report-scheduler.md new file mode 100644 index 0000000000..a2dad43d60 --- /dev/null +++ b/packages/backend-defaults/api-report-scheduler.md @@ -0,0 +1,16 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public +export const schedulerServiceFactory: () => ServiceFactory< + SchedulerService, + 'plugin' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/migrations/scheduler/20210928160613_init.js b/packages/backend-defaults/migrations/scheduler/20210928160613_init.js new file mode 100644 index 0000000000..f9900cab11 --- /dev/null +++ b/packages/backend-defaults/migrations/scheduler/20210928160613_init.js @@ -0,0 +1,64 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // + // tasks + // + await knex.schema.createTable('backstage_backend_tasks__tasks', table => { + table.comment('Tasks used for scheduling work on multiple workers'); + table + .string('id') + .primary() + .notNullable() + .comment('The unique ID of this particular task'); + table + .text('settings_json') + .notNullable() + .comment('JSON serialized object with properties for this task'); + table + .dateTime('next_run_start_at') + .notNullable() + .comment('The next time that the task should be started'); + table + .text('current_run_ticket') + .nullable() + .comment('A unique ticket for the current task run'); + table + .dateTime('current_run_started_at') + .nullable() + .comment('The time that the current task run started'); + table + .dateTime('current_run_expires_at') + .nullable() + .comment('The time that the current task run will time out'); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + // + // tasks + // + await knex.schema.dropTable('backstage_backend_tasks__tasks'); +}; diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index b5da7abbcf..742a1f99c1 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -12,6 +12,21 @@ "backstage": { "role": "node-library" }, + "exports": { + ".": "./src/index.ts", + "./scheduler": "./src/entrypoints/scheduler/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "scheduler": [ + "src/entrypoints/scheduler/index.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -34,14 +49,26 @@ "dependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", - "@backstage/plugin-events-node": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", + "@backstage/types": "workspace:^", + "@opentelemetry/api": "^1.3.0", + "cron": "^3.0.0", + "knex": "^3.0.0", + "lodash": "^4.17.21", + "luxon": "^3.0.0", + "uuid": "^9.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-test-utils": "workspace:^", - "@backstage/cli": "workspace:^" + "@backstage/cli": "workspace:^", + "wait-for-expect": "^3.0.2" }, "files": [ - "dist" + "dist", + "migrations" ] } diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index e9bdc03262..811f980d12 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -28,7 +28,6 @@ import { loggerServiceFactory, permissionsServiceFactory, rootLoggerServiceFactory, - schedulerServiceFactory, tokenManagerServiceFactory, urlReaderServiceFactory, identityServiceFactory, @@ -37,6 +36,7 @@ import { userInfoServiceFactory, } from '@backstage/backend-app-api'; import { eventsServiceFactory } from '@backstage/plugin-events-node'; +import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler'; export const defaultServiceFactories = [ authServiceFactory(), diff --git a/packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts b/packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts new file mode 100644 index 0000000000..959e6097f4 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolvePackagePath } from '@backstage/backend-common'; +import { Knex } from 'knex'; +import { DB_MIGRATIONS_TABLE } from './tables'; + +export async function migrateBackendTasks(knex: Knex): Promise { + const migrationsDir = resolvePackagePath( + '@backstage/backend-defaults', + 'migrations/scheduler', + ); + + await knex.migrate.latest({ + directory: migrationsDir, + tableName: DB_MIGRATIONS_TABLE, + }); +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts b/packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts new file mode 100644 index 0000000000..63aad6e42a --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DB_MIGRATIONS_TABLE = 'backstage_backend_tasks__knex_migrations'; +export const DB_TASKS_TABLE = 'backstage_backend_tasks__tasks'; + +export type DbTasksRow = { + id: string; + settings_json: string; + next_run_start_at: Date; + current_run_ticket?: string; + current_run_started_at?: Date | string; + current_run_expires_at?: Date | string; +}; diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/index.ts b/packages/backend-defaults/src/entrypoints/scheduler/index.ts similarity index 68% rename from packages/backend-plugin-api/src/entrypoints/scheduler/index.ts rename to packages/backend-defaults/src/entrypoints/scheduler/index.ts index 8c7dbf65f6..77d4ea561d 100644 --- a/packages/backend-plugin-api/src/entrypoints/scheduler/index.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/index.ts @@ -14,13 +14,4 @@ * limitations under the License. */ -export { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; -export type { - SchedulerService, - TaskDescriptor, - TaskFunction, - TaskInvocationDefinition, - TaskRunner, - TaskScheduleDefinition, - TaskScheduleDefinitionConfig, -} from './types'; +export { schedulerServiceFactory } from './schedulerServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts new file mode 100644 index 0000000000..4fd4949478 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { LocalTaskWorker } from './LocalTaskWorker'; + +describe('LocalTaskWorker', () => { + const logger = getVoidLogger(); + + it('runs the happy path (with iso duration) and handles cancellation', async () => { + const fn = jest.fn(); + const controller = new AbortController(); + + const worker = new LocalTaskWorker('a', fn, logger); + worker.start( + { + version: 2, + initialDelayDuration: 'PT0.2S', + cadence: 'PT0.2S', + timeoutAfterDuration: 'PT1S', + }, + { signal: controller.signal }, + ); + + // TODO(freben): Rewrite to fake timers - tried, but it wouldn't work + expect(fn).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 100)); + expect(fn).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 200)); + expect(fn).toHaveBeenCalledTimes(1); + await new Promise(r => setTimeout(r, 200)); + expect(fn).toHaveBeenCalledTimes(2); + controller.abort(); + await new Promise(r => setTimeout(r, 200)); + expect(fn).toHaveBeenCalledTimes(2); + }); + + it('runs the happy path (with a cron expression) and handles cancellation', async () => { + const fn = jest.fn(); + const controller = new AbortController(); + + // Await until system time is just past a second boundary (since cron is + // wall clock based) + await new Promise(r => setTimeout(r, 1000 - (Date.now() % 1000) + 10)); + + const worker = new LocalTaskWorker('a', fn, logger); + worker.start( + { + version: 2, + initialDelayDuration: 'PT0.2S', + cadence: '* * * * * *', + timeoutAfterDuration: 'PT1S', + }, + { signal: controller.signal }, + ); + + // TODO(freben): Rewrite to fake timers - tried, but it wouldn't work + expect(fn).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 100)); + expect(fn).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 200)); + expect(fn).toHaveBeenCalledTimes(1); + await new Promise(r => setTimeout(r, 1000)); + expect(fn).toHaveBeenCalledTimes(2); + controller.abort(); + await new Promise(r => setTimeout(r, 1000)); + expect(fn).toHaveBeenCalledTimes(2); + }); + + it('can trigger to abort wait', async () => { + const fn = jest.fn(); + const controller = new AbortController(); + + const worker = new LocalTaskWorker('a', fn, logger); + worker.start( + { + version: 2, + initialDelayDuration: 'PT0.2S', + cadence: 'PT0.2S', + timeoutAfterDuration: 'PT1S', + }, + { signal: controller.signal }, + ); + + // TODO(freben): Rewrite to fake timers - tried, but it wouldn't work + expect(fn).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 100)); + expect(fn).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 200)); + expect(fn).toHaveBeenCalledTimes(1); + worker.trigger(); + await new Promise(r => setTimeout(r, 10)); + expect(fn).toHaveBeenCalledTimes(2); + controller.abort(); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts new file mode 100644 index 0000000000..3510a855e9 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts @@ -0,0 +1,155 @@ +/* + * 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 { LoggerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api'; +import { ConflictError } from '@backstage/errors'; +import { CronTime } from 'cron'; +import { DateTime, Duration } from 'luxon'; +import { TaskSettingsV2 } from './types'; +import { delegateAbortController, sleep } from './util'; + +/** + * Implements tasks that run locally without cross-host collaboration. + * + * @private + */ +export class LocalTaskWorker { + private abortWait: AbortController | undefined; + + constructor( + private readonly taskId: string, + private readonly fn: SchedulerServiceTaskFunction, + private readonly logger: LoggerService, + ) {} + + start(settings: TaskSettingsV2, options?: { signal?: AbortSignal }) { + this.logger.info( + `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`, + ); + + (async () => { + let attemptNum = 1; + for (;;) { + try { + if (settings.initialDelayDuration) { + await this.sleep( + Duration.fromISO(settings.initialDelayDuration), + options?.signal, + ); + } + + while (!options?.signal?.aborted) { + const startTime = process.hrtime(); + await this.runOnce(settings, options?.signal); + const timeTaken = process.hrtime(startTime); + await this.waitUntilNext( + settings, + (timeTaken[0] + timeTaken[1] / 1e9) * 1000, + options?.signal, + ); + } + + this.logger.info(`Task worker finished: ${this.taskId}`); + attemptNum = 0; + break; + } catch (e) { + attemptNum += 1; + this.logger.warn( + `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`, + ); + await sleep(Duration.fromObject({ seconds: 1 })); + } + } + })(); + } + + trigger(): void { + if (!this.abortWait) { + throw new ConflictError(`Task ${this.taskId} is currently running`); + } + this.abortWait.abort(); + } + + /** + * Makes a single attempt at running the task to completion. + */ + private async runOnce( + settings: TaskSettingsV2, + signal?: AbortSignal, + ): Promise { + // Abort the task execution either if the worker is stopped, or if the + // task timeout is hit + const taskAbortController = delegateAbortController(signal); + const timeoutHandle = setTimeout(() => { + taskAbortController.abort(); + }, Duration.fromISO(settings.timeoutAfterDuration).as('milliseconds')); + + try { + await this.fn(taskAbortController.signal); + } catch (e) { + // ignore intentionally + } + + // release resources + clearTimeout(timeoutHandle); + taskAbortController.abort(); + } + + /** + * Sleeps until it's time to run the task again. + */ + private async waitUntilNext( + settings: TaskSettingsV2, + lastRunMillis: number, + signal?: AbortSignal, + ) { + if (signal?.aborted) { + return; + } + + const isCron = !settings.cadence.startsWith('P'); + let dt: number; + + if (isCron) { + const nextRun = +new CronTime(settings.cadence).sendAt().toJSDate(); + dt = nextRun - Date.now(); + } else { + dt = + Duration.fromISO(settings.cadence).as('milliseconds') - lastRunMillis; + } + + dt = Math.max(dt, 0); + + this.logger.debug( + `task: ${this.taskId} will next occur around ${DateTime.now().plus( + Duration.fromMillis(dt), + )}`, + ); + + await this.sleep(Duration.fromMillis(dt), signal); + } + + private async sleep( + duration: Duration, + abortSignal?: AbortSignal, + ): Promise { + this.abortWait = delegateAbortController(abortSignal); + await sleep(duration, this.abortWait.signal); + this.abortWait.abort(); // cleans up resources + this.abortWait = undefined; + } +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts new file mode 100644 index 0000000000..57faa6faf8 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts @@ -0,0 +1,349 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { ConflictError, NotFoundError } from '@backstage/errors'; +import { Duration } from 'luxon'; +import { migrateBackendTasks } from '../database/migrateBackendTasks'; +import { + PluginTaskSchedulerImpl, + parseDuration, +} from './PluginTaskSchedulerImpl'; + +function defer() { + let resolve = () => {}; + const promise = new Promise(_resolve => { + resolve = _resolve; + }); + return { promise, resolve }; +} + +jest.setTimeout(60_000); + +describe('PluginTaskManagerImpl', () => { + const databases = TestDatabases.create({ + ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], + }); + + beforeAll(async () => { + // Make sure all databases are running before mocking timers, in case of testcontainers + await Promise.all( + databases.eachSupportedId().map(([id]) => databases.init(id)), + ); + + jest.useFakeTimers(); + }, 60_000); + + async function init(databaseId: TestDatabaseId) { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + const manager = new PluginTaskSchedulerImpl( + async () => knex, + getVoidLogger(), + ); + return { knex, manager }; + } + + // This is just to test the wrapper code; most of the actual tests are in + // TaskWorker.test.ts + describe('scheduleTask with global scope', () => { + it.each(databases.eachSupportedId())( + 'can run the v1 happy path, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromMillis(5000), + fn, + scope: 'global', + }); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, + ); + + it.each(databases.eachSupportedId())( + 'can run the v2 happy path, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager.scheduleTask({ + id: 'task2', + timeout: Duration.fromMillis(5000), + frequency: { cron: '* * * * * *' }, + fn, + scope: 'global', + }); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, + ); + }); + + describe('triggerTask with global scope', () => { + it.each(databases.eachSupportedId())( + 'can manually trigger a task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + initialDelay: Duration.fromObject({ years: 1 }), + fn, + scope: 'global', + }); + + await manager.triggerTask('task1'); + jest.advanceTimersByTime(5000); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, + ); + + it.each(databases.eachSupportedId())( + 'cant trigger a non-existent task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn, + scope: 'global', + }); + + await expect(() => manager.triggerTask('task2')).rejects.toThrow( + NotFoundError, + ); + }, + ); + + it.each(databases.eachSupportedId())( + 'cant trigger a running task, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const { promise, resolve } = defer(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn: async () => { + resolve(); + await new Promise(r => setTimeout(r, 20000)); + }, + scope: 'global', + }); + + await promise; + await expect(() => manager.triggerTask('task1')).rejects.toThrow( + ConflictError, + ); + }, + ); + }); + + // This is just to test the wrapper code; most of the actual tests are in + // TaskWorker.test.ts + describe('scheduleTask with local scope', () => { + it('can run the v1 happy path', async () => { + const { manager } = await init('SQLITE_3'); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager.scheduleTask({ + id: 'task1', + timeout: { milliseconds: 5000 }, + frequency: { milliseconds: 5000 }, + fn, + scope: 'local', + }); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, 60_000); + + it('can run the v2 happy path', async () => { + const { manager } = await init('SQLITE_3'); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager.scheduleTask({ + id: 'task2', + timeout: Duration.fromMillis(5000), + frequency: { cron: '* * * * * *' }, + fn, + scope: 'local', + }); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, 60_000); + }); + + describe('triggerTask with local scope', () => { + it('can manually trigger a task', async () => { + const { manager } = await init('SQLITE_3'); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + initialDelay: Duration.fromObject({ years: 1 }), + fn, + scope: 'local', + }); + + await manager.triggerTask('task1'); + jest.advanceTimersByTime(5000); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, 60_000); + + it('cant trigger a non-existent task', async () => { + const { manager } = await init('SQLITE_3'); + + const fn = jest.fn(); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn, + scope: 'local', + }); + + await expect(() => manager.triggerTask('task2')).rejects.toThrow( + NotFoundError, + ); + }, 60_000); + + it('cant trigger a running task', async () => { + const { manager } = await init('SQLITE_3'); + + const { promise, resolve } = defer(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromObject({ years: 1 }), + fn: async () => { + resolve(); + await new Promise(r => setTimeout(r, 20000)); + }, + scope: 'local', + }); + + await promise; + await expect(() => manager.triggerTask('task1')).rejects.toThrow( + ConflictError, + ); + }, 60_000); + }); + + // This is just to test the wrapper code; most of the actual tests are in + // TaskWorker.test.ts + describe('createScheduledTaskRunner', () => { + it.each(databases.eachSupportedId())( + 'can run the happy path, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + const promise = new Promise(resolve => fn.mockImplementation(resolve)); + await manager + .createScheduledTaskRunner({ + timeout: Duration.fromMillis(5000), + frequency: Duration.fromMillis(5000), + scope: 'global', + }) + .run({ + id: 'task1', + fn, + }); + + await promise; + expect(fn).toHaveBeenCalledWith(expect.any(AbortSignal)); + }, + ); + }); + + describe('can fetch task ids', () => { + it.each(databases.eachSupportedId())( + 'can fetch both global and local task ids, %p', + async databaseId => { + const { manager } = await init(databaseId); + const fn = jest.fn(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromMillis(5000), + fn, + scope: 'global', + }); + + await manager.scheduleTask({ + id: 'task2', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromMillis(5000), + fn, + scope: 'local', + }); + + await expect(manager.getScheduledTasks()).resolves.toEqual([ + { + id: 'task1', + scope: 'global', + settings: expect.objectContaining({ cadence: 'PT5S' }), + }, + { + id: 'task2', + scope: 'local', + settings: expect.objectContaining({ cadence: 'PT5S' }), + }, + ]); + }, + ); + }); + + describe('parseDuration', () => { + it('should parse durations', () => { + expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S'); + expect(parseDuration(Duration.fromMillis(5000))).toEqual('PT5S'); + expect(parseDuration({ cron: '1 * * * *' })).toEqual('1 * * * *'); + }); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts new file mode 100644 index 0000000000..ed14d75db2 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts @@ -0,0 +1,169 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { + SchedulerServiceTaskDescriptor, + SchedulerServiceTaskFunction, + SchedulerServiceTaskInvocationDefinition, + SchedulerServiceTaskRunner, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; +import { Counter, Histogram, metrics } from '@opentelemetry/api'; +import { Knex } from 'knex'; +import { Duration } from 'luxon'; +import { LocalTaskWorker } from './LocalTaskWorker'; +import { TaskWorker } from './TaskWorker'; +import { PluginTaskScheduler, TaskSettingsV2 } from './types'; +import { validateId } from './util'; + +/** + * Implements the actual task management. + */ +export class PluginTaskSchedulerImpl implements PluginTaskScheduler { + private readonly localTasksById = new Map(); + private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = []; + + private readonly counter: Counter; + private readonly duration: Histogram; + + constructor( + private readonly databaseFactory: () => Promise, + private readonly logger: LoggerService, + ) { + const meter = metrics.getMeter('default'); + this.counter = meter.createCounter('backend_tasks.task.runs.count', { + description: 'Total number of times a task has been run', + }); + this.duration = meter.createHistogram('backend_tasks.task.runs.duration', { + description: 'Histogram of task run durations', + unit: 'seconds', + }); + } + + async triggerTask(id: string): Promise { + const localTask = this.localTasksById.get(id); + if (localTask) { + localTask.trigger(); + return; + } + + const knex = await this.databaseFactory(); + await TaskWorker.trigger(knex, id); + } + + async scheduleTask( + task: SchedulerServiceTaskScheduleDefinition & + SchedulerServiceTaskInvocationDefinition, + ): Promise { + validateId(task.id); + const scope = task.scope ?? 'global'; + + const settings: TaskSettingsV2 = { + version: 2, + cadence: parseDuration(task.frequency), + initialDelayDuration: + task.initialDelay && parseDuration(task.initialDelay), + timeoutAfterDuration: parseDuration(task.timeout), + }; + + if (scope === 'global') { + const knex = await this.databaseFactory(); + const worker = new TaskWorker( + task.id, + this.wrapInMetrics(task.fn, { labels: { taskId: task.id, scope } }), + knex, + this.logger.child({ task: task.id }), + ); + await worker.start(settings, { signal: task.signal }); + } else { + const worker = new LocalTaskWorker( + task.id, + this.wrapInMetrics(task.fn, { labels: { taskId: task.id, scope } }), + this.logger.child({ task: task.id }), + ); + worker.start(settings, { signal: task.signal }); + this.localTasksById.set(task.id, worker); + } + + this.allScheduledTasks.push({ + id: task.id, + scope: scope, + settings: settings, + }); + } + + createScheduledTaskRunner( + schedule: SchedulerServiceTaskScheduleDefinition, + ): SchedulerServiceTaskRunner { + return { + run: async task => { + await this.scheduleTask({ ...task, ...schedule }); + }, + }; + } + + async getScheduledTasks(): Promise { + return this.allScheduledTasks; + } + + private wrapInMetrics( + fn: SchedulerServiceTaskFunction, + opts: { labels: Record }, + ): SchedulerServiceTaskFunction { + return async abort => { + const labels = { + ...opts.labels, + }; + this.counter.add(1, { ...labels, result: 'started' }); + + const startTime = process.hrtime(); + + try { + await fn(abort); + labels.result = 'completed'; + } catch (ex) { + labels.result = 'failed'; + throw ex; + } finally { + const delta = process.hrtime(startTime); + const endTime = delta[0] + delta[1] / 1e9; + this.counter.add(1, labels); + this.duration.record(endTime, labels); + } + }; + } +} + +export function parseDuration( + frequency: SchedulerServiceTaskScheduleDefinition['frequency'], +): string { + if ('cron' in frequency) { + return frequency.cron; + } + + const parsed = Duration.isDuration(frequency) + ? frequency + : Duration.fromObject(frequency); + + if (!parsed.isValid) { + throw new Error( + `Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`, + ); + } + + return parsed.toISO()!; +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts new file mode 100644 index 0000000000..82133a9e54 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import { Knex } from 'knex'; +import { Duration } from 'luxon'; +import waitForExpect from 'wait-for-expect'; +import { migrateBackendTasks } from '../database/migrateBackendTasks'; +import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables'; +import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor'; +import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; + +const insertTask = async (knex: Knex, task: DbTasksRow) => { + return knex(DB_TASKS_TABLE) + .insert(task) + .onConflict('id') + .merge(['settings_json']); +}; + +const getTask = async (knex: Knex): Promise => { + return (await knex(DB_TASKS_TABLE))[0]; +}; + +describe('PluginTaskSchedulerJanitor', () => { + const logger = getVoidLogger(); + const databases = TestDatabases.create({ + ids: [ + /* 'MYSQL_8' not supported yet */ + 'POSTGRES_16', + 'POSTGRES_12', + 'SQLITE_3', + 'MYSQL_8', + ], + }); + const testScopedSignal = createTestScopedSignal(); + + jest.setTimeout(60_000); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it.each(databases.eachSupportedId())( + 'Should update date if current_run_expires_at expires, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const dateYesterday = new Date( + new Date().setDate(new Date().getDate() - 1), + ); + + await insertTask(knex, { + id: 'task1', + settings_json: '', + next_run_start_at: new Date('2023-03-01 00:00:00'), + current_run_ticket: 'ticket', + current_run_started_at: dateYesterday, + current_run_expires_at: dateYesterday, + }); + + const worker = new PluginTaskSchedulerJanitor({ + waitBetweenRuns: Duration.fromObject({ milliseconds: 20 }), + knex, + logger, + }); + + worker.start(testScopedSignal()); + + await waitForExpect(async () => { + await expect(getTask(knex)).resolves.toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }), + ); + }); + }, + ); +}); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts new file mode 100644 index 0000000000..b0cd4572cf --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts @@ -0,0 +1,96 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { Knex } from 'knex'; +import { Duration } from 'luxon'; +import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables'; +import { sleep } from './util'; + +/** + * Makes sure to auto-expire and clean up things that time out or for other + * reasons should not be left lingering. + */ +export class PluginTaskSchedulerJanitor { + private readonly knex: Knex; + private readonly waitBetweenRuns: Duration; + private readonly logger: LoggerService; + + constructor(options: { + knex: Knex; + waitBetweenRuns: Duration; + logger: LoggerService; + }) { + this.knex = options.knex; + this.waitBetweenRuns = options.waitBetweenRuns; + this.logger = options.logger; + } + + async start(abortSignal?: AbortSignal) { + while (!abortSignal?.aborted) { + try { + await this.runOnce(); + } catch (e) { + this.logger.warn(`Error while performing janitorial tasks, ${e}`); + } + + await sleep(this.waitBetweenRuns, abortSignal); + } + } + + private async runOnce() { + const dbNull = this.knex.raw('null'); + const configClient = this.knex.client.config.client; + + let tasks: Array<{ id: string }>; + if (configClient.includes('sqlite3') || configClient.includes('mysql')) { + tasks = await this.knex(DB_TASKS_TABLE) + .select('id') + .where('current_run_expires_at', '<', this.knex.fn.now()); + await this.knex(DB_TASKS_TABLE) + .whereIn( + 'id', + tasks.map(t => t.id), + ) + .update({ + current_run_ticket: dbNull, + current_run_started_at: dbNull, + current_run_expires_at: dbNull, + }); + } else { + tasks = await this.knex(DB_TASKS_TABLE) + .where('current_run_expires_at', '<', this.knex.fn.now()) + .update({ + current_run_ticket: dbNull, + current_run_started_at: dbNull, + current_run_expires_at: dbNull, + }) + .returning(['id']); + } + + // In rare cases, knex drivers may ignore "returning", and return the number + // of rows changed instead + if (typeof tasks === 'number') { + if (tasks > 0) { + this.logger.warn(`${tasks} tasks timed out and were lost`); + } + } else { + for (const { id } of tasks) { + this.logger.warn(`Task timed out and was lost: ${id}`); + } + } + } +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.test.ts new file mode 100644 index 0000000000..d286eaee38 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; +import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { Duration } from 'luxon'; +import waitForExpect from 'wait-for-expect'; +import { TaskScheduler } from './TaskScheduler'; +import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; + +jest.setTimeout(60_000); + +describe('TaskScheduler', () => { + const logger = getVoidLogger(); + const databases = TestDatabases.create(); + const testScopedSignal = createTestScopedSignal(); + + async function createDatabase( + databaseId: TestDatabaseId, + ): Promise { + const knex = await databases.init(databaseId); + const databaseManager: Partial = { + forPlugin: () => ({ + getClient: async () => knex, + }), + }; + return databaseManager as DatabaseManager; + } + + it.each(databases.eachSupportedId())( + 'can return a working v1 plugin impl, %p', + async databaseId => { + const database = await createDatabase(databaseId); + const manager = new TaskScheduler(database, logger).forPlugin('test'); + const fn = jest.fn(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromMillis(5000), + signal: testScopedSignal(), + fn, + }); + + await waitForExpect(() => { + expect(fn).toHaveBeenCalled(); + }); + }, + ); + + it.each(databases.eachSupportedId())( + 'can return a working v2 plugin impl, %p', + async databaseId => { + const database = await createDatabase(databaseId); + const manager = new TaskScheduler(database, logger).forPlugin('test'); + const fn = jest.fn(); + + await manager.scheduleTask({ + id: 'task2', + timeout: Duration.fromMillis(5000), + frequency: { cron: '* * * * * *' }, + signal: testScopedSignal(), + fn, + }); + + await waitForExpect(() => { + expect(fn).toHaveBeenCalled(); + }); + }, + ); +}); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts new file mode 100644 index 0000000000..0757d680ae --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + DatabaseManager, + getRootLogger, + LegacyRootDatabaseService, + PluginDatabaseManager, +} from '@backstage/backend-common'; +import { + LoggerService, + RootConfigService, +} from '@backstage/backend-plugin-api'; +import { once } from 'lodash'; +import { Duration } from 'luxon'; +import { migrateBackendTasks } from '../database/migrateBackendTasks'; +import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl'; +import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor'; +import { PluginTaskScheduler } from './types'; + +/** + * Deals with the scheduling of distributed tasks. + */ +export class TaskScheduler { + static fromConfig( + config: RootConfigService, + options?: { + databaseManager?: LegacyRootDatabaseService; + logger?: LoggerService; + }, + ): TaskScheduler { + const databaseManager = + options?.databaseManager ?? DatabaseManager.fromConfig(config); + const logger = (options?.logger || getRootLogger()).child({ + type: 'taskManager', + }); + return new TaskScheduler(databaseManager, logger); + } + + constructor( + private readonly databaseManager: LegacyRootDatabaseService, + private readonly logger: LoggerService, + ) {} + + /** + * Instantiates a task manager instance for the given plugin. + * + * @param pluginId - The unique ID of the plugin, for example "catalog" + * @returns A {@link PluginTaskScheduler} instance + */ + forPlugin(pluginId: string): PluginTaskScheduler { + return TaskScheduler.forPlugin({ + pluginId, + databaseManager: this.databaseManager.forPlugin(pluginId), + logger: this.logger, + }); + } + + static forPlugin(opts: { + pluginId: string; + databaseManager: PluginDatabaseManager; + logger: LoggerService; + }): PluginTaskScheduler { + const databaseFactory = once(async () => { + const knex = await opts.databaseManager.getClient(); + + if (!opts.databaseManager.migrations?.skip) { + await migrateBackendTasks(knex); + } + + if (process.env.NODE_ENV !== 'test') { + const janitor = new PluginTaskSchedulerJanitor({ + knex, + waitBetweenRuns: Duration.fromObject({ minutes: 1 }), + logger: opts.logger, + }); + janitor.start(); + } + + return knex; + }); + + return new PluginTaskSchedulerImpl(databaseFactory, opts.logger); + } +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts new file mode 100644 index 0000000000..20d9b63859 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts @@ -0,0 +1,507 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import { DateTime, Duration } from 'luxon'; +import waitForExpect from 'wait-for-expect'; +import { migrateBackendTasks } from '../database/migrateBackendTasks'; +import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables'; +import { TaskWorker } from './TaskWorker'; +import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal'; +import { TaskSettingsV2 } from './types'; + +jest.setTimeout(60_000); + +describe('TaskWorker', () => { + const logger = getVoidLogger(); + const databases = TestDatabases.create(); + const testScopedSignal = createTestScopedSignal(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it.each(databases.eachSupportedId())( + 'goes through the expected states, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + const settings: TaskSettingsV2 = { + version: 2, + cadence: '*/2 * * * * *', + initialDelayDuration: Duration.fromObject({ seconds: 1 }).toISO()!, + timeoutAfterDuration: Duration.fromObject({ minutes: 1 }).toISO()!, + }; + + const worker = new TaskWorker('task1', fn, knex, logger); + await worker.persistTask(settings); + + let row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }), + ); + expect(JSON.parse(row.settings_json)).toEqual({ + version: 2, + cadence: '*/2 * * * * *', + initialDelayDuration: 'PT1S', + timeoutAfterDuration: 'PT1M', + }); + + await expect(worker.findReadyTask()).resolves.toEqual({ + result: 'not-ready-yet', + }); + + await waitForExpect(async () => { + await expect(worker.findReadyTask()).resolves.toEqual({ + result: 'ready', + settings, + }); + }); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }), + ); + + await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: 'ticket', + current_run_started_at: expect.anything(), + current_run_expires_at: expect.anything(), + }), + ); + + await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe( + true, + ); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }), + ); + }, + ); + + it.each(databases.eachSupportedId())( + 'logs error when the task throws, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + jest.spyOn(logger, 'error'); + const fn = jest.fn().mockRejectedValue(new Error('failed')); + const settings: TaskSettingsV2 = { + version: 2, + initialDelayDuration: undefined, + cadence: '* * * * * *', + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, + }; + const checkFrequency = Duration.fromObject({ milliseconds: 100 }); + const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); + worker.start(settings, { signal: testScopedSignal() }); + + await waitForExpect(() => { + expect(logger.error).toHaveBeenCalled(); + }); + }, + ); + + it.each(databases.eachSupportedId())( + 'runs tasks more than once even when the task throws, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn().mockRejectedValue(new Error('failed')); + const settings: TaskSettingsV2 = { + version: 2, + initialDelayDuration: undefined, + cadence: '* * * * * *', + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, + }; + const checkFrequency = Duration.fromObject({ milliseconds: 100 }); + const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); + worker.start(settings, { signal: testScopedSignal() }); + + await waitForExpect(() => { + expect(fn).toHaveBeenCalledTimes(3); + }); + }, + ); + + it.each(databases.eachSupportedId())( + 'does not clobber ticket lock when stolen, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + const settings: TaskSettingsV2 = { + version: 2, + initialDelayDuration: undefined, + cadence: '* * * * * *', + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, + }; + + const worker = new TaskWorker('task1', fn, knex, logger); + await worker.persistTask(settings); + + await waitForExpect(async () => { + await expect(worker.findReadyTask()).resolves.toEqual({ + result: 'ready', + settings, + }); + }); + + await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true); + + let row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: 'ticket', + current_run_started_at: expect.anything(), + current_run_expires_at: expect.anything(), + }), + ); + + await knex(DB_TASKS_TABLE) + .where('id', '=', 'task1') + .update({ current_run_ticket: 'stolen' }); + + await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe( + false, + ); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: 'stolen', + current_run_started_at: expect.anything(), + current_run_expires_at: expect.anything(), + }), + ); + }, + ); + + it.each(databases.eachSupportedId())( + 'gracefully handles a disappeared task row, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn(async () => {}); + const settings: TaskSettingsV2 = { + version: 2, + initialDelayDuration: undefined, + cadence: '* * * * * *', + timeoutAfterDuration: Duration.fromMillis(60000).toISO()!, + }; + + const worker1 = new TaskWorker('task1', fn, knex, logger); + await worker1.persistTask(settings); + await knex(DB_TASKS_TABLE).where('id', '=', 'task1').delete(); + await expect(worker1.findReadyTask()).resolves.toEqual({ + result: 'abort', + }); + + const worker2 = new TaskWorker('task2', fn, knex, logger); + await worker2.persistTask(settings); + + await waitForExpect(async () => { + await expect(worker2.findReadyTask()).resolves.toEqual({ + result: 'ready', + settings, + }); + }); + + await knex(DB_TASKS_TABLE).where('id', '=', 'task2').delete(); + await expect(worker2.tryClaimTask('ticket', settings)).resolves.toBe( + false, + ); + + const worker3 = new TaskWorker('task3', fn, knex, logger); + await worker3.persistTask(settings); + + await waitForExpect(async () => { + await expect(worker3.findReadyTask()).resolves.toEqual({ + result: 'ready', + settings, + }); + }); + + await expect(worker3.tryClaimTask('ticket', settings)).resolves.toBe( + true, + ); + await knex(DB_TASKS_TABLE).where('id', '=', 'task3').delete(); + await expect(worker3.tryReleaseTask('ticket', settings)).resolves.toBe( + false, + ); + }, + ); + + it.each(databases.eachSupportedId())( + 'respects initialDelayDuration per worker, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const abortFirst = new AbortController(); + const settings: TaskSettingsV2 = { + version: 2, + initialDelayDuration: 'PT0.3S', + cadence: 'PT0.1S', + timeoutAfterDuration: 'PT10S', + }; + + // Start a single worker and make sure it waits and then goes to work + const fn1 = jest.fn(async () => {}); + const worker1 = new TaskWorker( + 'task1', + fn1, + knex, + logger, + Duration.fromMillis(10), + ); + await worker1.start(settings, { signal: abortFirst.signal }); + + expect(fn1).toHaveBeenCalledTimes(0); + await new Promise(resolve => setTimeout(resolve, 250)); + expect(fn1).toHaveBeenCalledTimes(0); + await new Promise(resolve => setTimeout(resolve, 100)); + expect(fn1.mock.calls.length).toBeGreaterThan(0); + + // Start a second worker and make sure it waits but the first worker still works along + const fn2 = jest.fn(); + const promise2 = new Promise(resolve => fn2.mockImplementation(resolve)); + const worker2 = new TaskWorker( + 'task1', + fn2, + knex, + logger, + Duration.fromMillis(10), + ); + await worker2.start(settings, { signal: testScopedSignal() }); + + // We eventually abort the first worker just to make sure that the second + // one for sure will get a go at running the task + setTimeout(() => abortFirst.abort(), 1000); + + const before = fn1.mock.calls.length; + await promise2; + expect(fn1.mock.calls.length).toBeGreaterThan(before); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + 'next_run_start_at is always the min between schedule changes from cron frequency, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + const settings: TaskSettingsV2 = { + version: 2, + cadence: '*/15 * * * *', + initialDelayDuration: 'PT2M', + timeoutAfterDuration: 'PT1M', + }; + + const worker = new TaskWorker('task99', fn, knex, logger); + await worker.persistTask(settings); + const row1 = (await knex(DB_TASKS_TABLE))[0]; + + const settings2 = { + ...settings, + cadence: '*/2 * * * *', + initialDelayDuration: 'PT1M', + }; + await worker.persistTask(settings2); + const row2 = (await knex(DB_TASKS_TABLE))[0]; + + expect(row2.next_run_start_at).not.toStrictEqual(row1.next_run_start_at); + + const settings3 = { ...settings }; + await worker.persistTask(settings3); + const row3 = (await knex(DB_TASKS_TABLE))[0]; + + // The new timestamp can basically be 0 or a minute depending on how the + // initialDelayDuration falls right on a cron boundary. This kinda + // contrived check removes a test flakiness based on wall clock time. + expect( + Math.abs( + +new Date(row3.next_run_start_at) - +new Date(row2.next_run_start_at), + ), + ).toBeLessThanOrEqual(60_000); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + 'next_run_start_at is always the min between schedule changes when using human duration frequency, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + + const initialSettings: TaskSettingsV2 = { + version: 2, + cadence: 'PT120M', + timeoutAfterDuration: 'PT1M', + }; + + const worker = new TaskWorker('task99', fn, knex, logger); + await worker.persistTask(initialSettings); + // replicate task running, sets next_run_start_at based on cadence + await worker.tryClaimTask('ticket', initialSettings); + await worker.tryReleaseTask('ticket', initialSettings); + + // grab initial row for comparisons later + const rowAfterClaimAndRelease = ( + await knex(DB_TASKS_TABLE) + )[0]; + + const settings: TaskSettingsV2 = { + ...initialSettings, + cadence: 'PT60M', + }; + await worker.persistTask(settings); + const row1 = (await knex(DB_TASKS_TABLE))[0]; + + const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate( + new Date(rowAfterClaimAndRelease.next_run_start_at), + ); + const row1NextStartAt = DateTime.fromJSDate( + new Date(row1.next_run_start_at), + ); + const now = DateTime.now(); + expect( + rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'), + ).toBeCloseTo(60, 1); // ensure that next start at is sooner than initial by one hour + expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour + expect( + rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'), + ).toBeCloseTo(120, 1); + + const settings2 = { + ...settings, + }; + await worker.persistTask(settings2); + const row2 = (await knex(DB_TASKS_TABLE))[0]; + + expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + 'next_run_start_at is always the min between schedule changes when using human duration frequency with initial start delay, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + + const initialSettings: TaskSettingsV2 = { + version: 2, + cadence: 'PT120M', + initialDelayDuration: 'PT2M', + timeoutAfterDuration: 'PT1M', + }; + + const worker = new TaskWorker('task99', fn, knex, logger); + await worker.persistTask(initialSettings); + // replicate task running, sets next_run_start_at based on cadence + await worker.tryClaimTask('ticket', initialSettings); + await worker.tryReleaseTask('ticket', initialSettings); + + // grab initial row for comparisons later + const rowAfterClaimAndRelease = ( + await knex(DB_TASKS_TABLE) + )[0]; + + const settings: TaskSettingsV2 = { + ...initialSettings, + cadence: 'PT60M', + }; + await worker.persistTask(settings); + const row1 = (await knex(DB_TASKS_TABLE))[0]; + + const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate( + new Date(rowAfterClaimAndRelease.next_run_start_at), + ); + const row1NextStartAt = DateTime.fromJSDate( + new Date(row1.next_run_start_at), + ); + const now = DateTime.now(); + expect( + rowAfterClaimAndReleaseNextStartAt.diff(row1NextStartAt).as('minutes'), + ).toBeCloseTo(62, 1); // ensure that next start at is sooner than initial by one hour, plus the 2 minute delay (set my tryReleaseTask) + expect(row1NextStartAt.diff(now).as('minutes')).toBeCloseTo(60, 1); // ensure that next start at is later than now by one hour (2 minute delay doesn't take effect here) + expect( + rowAfterClaimAndReleaseNextStartAt.diff(now).as('minutes'), + ).toBeCloseTo(122, 1); // includes 2 minute start delay (which is persisted from tryReleaseTask) + + const settings2 = { + ...settings, + }; + await worker.persistTask(settings2); + const row2 = (await knex(DB_TASKS_TABLE))[0]; + + expect(row2.next_run_start_at).toStrictEqual(row1.next_run_start_at); + + await knex.destroy(); + }, + ); +}); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts new file mode 100644 index 0000000000..a1c4ec44fd --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts @@ -0,0 +1,373 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { ConflictError, NotFoundError } from '@backstage/errors'; +import { CronTime } from 'cron'; +import { Knex } from 'knex'; +import { DateTime, Duration } from 'luxon'; +import { v4 as uuid } from 'uuid'; +import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables'; +import { TaskSettingsV2, taskSettingsV2Schema } from './types'; +import { delegateAbortController, nowPlus, sleep } from './util'; +import { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api'; + +const DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 }); + +/** + * Implements tasks that run across worker hosts, with collaborative locking. + * + * @private + */ +export class TaskWorker { + constructor( + private readonly taskId: string, + private readonly fn: SchedulerServiceTaskFunction, + private readonly knex: Knex, + private readonly logger: LoggerService, + private readonly workCheckFrequency: Duration = DEFAULT_WORK_CHECK_FREQUENCY, + ) {} + + async start(settings: TaskSettingsV2, options?: { signal?: AbortSignal }) { + try { + await this.persistTask(settings); + } catch (e) { + throw new Error(`Failed to persist task, ${e}`); + } + + this.logger.info( + `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`, + ); + + let workCheckFrequency = this.workCheckFrequency; + const isCron = !settings?.cadence.startsWith('P'); + if (!isCron) { + const cadence = Duration.fromISO(settings.cadence); + if (cadence < workCheckFrequency) { + workCheckFrequency = cadence; + } + } + + let attemptNum = 1; + (async () => { + for (;;) { + try { + if (settings.initialDelayDuration) { + await sleep( + Duration.fromISO(settings.initialDelayDuration), + options?.signal, + ); + } + + while (!options?.signal?.aborted) { + const runResult = await this.runOnce(options?.signal); + + if (runResult.result === 'abort') { + break; + } + + await sleep(workCheckFrequency, options?.signal); + } + + this.logger.info(`Task worker finished: ${this.taskId}`); + attemptNum = 0; + break; + } catch (e) { + attemptNum += 1; + this.logger.warn( + `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`, + ); + await sleep(Duration.fromObject({ seconds: 1 })); + } + } + })(); + } + + static async trigger(knex: Knex, taskId: string): Promise { + // check if task exists + const rows = await knex(DB_TASKS_TABLE) + .select(knex.raw(1)) + .where('id', '=', taskId); + if (rows.length !== 1) { + throw new NotFoundError(`Task ${taskId} does not exist`); + } + + const updatedRows = await knex(DB_TASKS_TABLE) + .where('id', '=', taskId) + .whereNull('current_run_ticket') + .update({ + next_run_start_at: knex.fn.now(), + }); + if (updatedRows < 1) { + throw new ConflictError(`Task ${taskId} is currently running`); + } + } + + /** + * Makes a single attempt at running the task to completion, if ready. + * + * @returns The outcome of the attempt + */ + private async runOnce( + signal?: AbortSignal, + ): Promise< + | { result: 'not-ready-yet' } + | { result: 'abort' } + | { result: 'failed' } + | { result: 'completed' } + > { + const findResult = await this.findReadyTask(); + if ( + findResult.result === 'not-ready-yet' || + findResult.result === 'abort' + ) { + return findResult; + } + + const taskSettings = findResult.settings; + const ticket = uuid(); + + const claimed = await this.tryClaimTask(ticket, taskSettings); + if (!claimed) { + return { result: 'not-ready-yet' }; + } + + // Abort the task execution either if the worker is stopped, or if the + // task timeout is hit + const taskAbortController = delegateAbortController(signal); + const timeoutHandle = setTimeout(() => { + taskAbortController.abort(); + }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds')); + + try { + await this.fn(taskAbortController.signal); + taskAbortController.abort(); // releases resources + } catch (e) { + this.logger.error(e); + await this.tryReleaseTask(ticket, taskSettings); + return { result: 'failed' }; + } finally { + clearTimeout(timeoutHandle); + } + + await this.tryReleaseTask(ticket, taskSettings); + return { result: 'completed' }; + } + + /** + * Perform the initial store of the task info + */ + async persistTask(settings: TaskSettingsV2) { + // Perform an initial parse to ensure that we will definitely be able to + // read it back again. + taskSettingsV2Schema.parse(settings); + + const isCron = !settings?.cadence.startsWith('P'); + + let startAt: Knex.Raw | undefined; + let nextStartAt: Knex.Raw | undefined; + if (settings.initialDelayDuration) { + startAt = nowPlus( + Duration.fromISO(settings.initialDelayDuration), + this.knex, + ); + } + + if (isCron) { + const time = new CronTime(settings.cadence) + .sendAt() + .minus({ seconds: 1 }) // immediately, if "* * * * * *" + .toUTC(); + + nextStartAt = this.nextRunAtRaw(time); + startAt ||= nextStartAt; + } else { + startAt ||= this.knex.fn.now(); + nextStartAt = nowPlus(Duration.fromISO(settings.cadence), this.knex); + } + + this.logger.debug(`task: ${this.taskId} configured to run at: ${startAt}`); + + // It's OK if the task already exists; if it does, just replace its + // settings with the new value and start the loop as usual. + const settingsJson = JSON.stringify(settings); + await this.knex(DB_TASKS_TABLE) + .insert({ + id: this.taskId, + settings_json: settingsJson, + next_run_start_at: startAt, + }) + .onConflict('id') + .merge( + this.knex.client.config.client.includes('mysql') + ? { + settings_json: settingsJson, + next_run_start_at: this.knex.raw( + `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`, + [ + nextStartAt, + 'next_run_start_at', + nextStartAt, + 'next_run_start_at', + ], + ), + } + : { + settings_json: this.knex.ref('excluded.settings_json'), + next_run_start_at: this.knex.raw( + `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`, + [ + nextStartAt, + `${DB_TASKS_TABLE}.next_run_start_at`, + nextStartAt, + `${DB_TASKS_TABLE}.next_run_start_at`, + ], + ), + }, + ); + } + + /** + * Check if the task is ready to run + */ + async findReadyTask(): Promise< + | { result: 'not-ready-yet' } + | { result: 'abort' } + | { result: 'ready'; settings: TaskSettingsV2 } + > { + const [row] = await this.knex(DB_TASKS_TABLE) + .where('id', '=', this.taskId) + .select({ + settingsJson: 'settings_json', + ready: this.knex.raw( + `CASE + WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE + ELSE FALSE + END`, + [this.knex.fn.now()], + ), + }); + + if (!row) { + this.logger.info( + 'No longer able to find task; aborting and assuming that it has been unregistered or expired', + ); + return { result: 'abort' }; + } else if (!row.ready) { + return { result: 'not-ready-yet' }; + } + + try { + const obj = JSON.parse(row.settingsJson); + const settings = taskSettingsV2Schema.parse(obj); + return { result: 'ready', settings }; + } catch (e) { + this.logger.info( + `Task "${this.taskId}" is no longer able to parse task settings; aborting and assuming that a ` + + `newer version of the task has been issued and being handled by other workers, ${e}`, + ); + return { result: 'abort' }; + } + } + + /** + * Attempts to claim a task that's ready for execution, on this worker's + * behalf. We should not attempt to perform the work unless the claim really + * goes through. + * + * @param ticket - A globally unique string that changes for each invocation + * @param settings - The settings of the task to claim + * @returns True if it was successfully claimed + */ + async tryClaimTask( + ticket: string, + settings: TaskSettingsV2, + ): Promise { + const startedAt = this.knex.fn.now(); + const expiresAt = settings.timeoutAfterDuration + ? nowPlus(Duration.fromISO(settings.timeoutAfterDuration), this.knex) + : this.knex.raw('null'); + + const rows = await this.knex(DB_TASKS_TABLE) + .where('id', '=', this.taskId) + .whereNull('current_run_ticket') + .update({ + current_run_ticket: ticket, + current_run_started_at: startedAt, + current_run_expires_at: expiresAt, + }); + + return rows === 1; + } + + async tryReleaseTask( + ticket: string, + settings: TaskSettingsV2, + ): Promise { + const isCron = !settings?.cadence.startsWith('P'); + + let nextRun: Knex.Raw; + if (isCron) { + const time = new CronTime(settings.cadence).sendAt().toUTC(); + this.logger.debug(`task: ${this.taskId} will next occur around ${time}`); + + nextRun = this.nextRunAtRaw(time); + } else { + const dt = Duration.fromISO(settings.cadence).as('seconds'); + this.logger.debug( + `task: ${this.taskId} will next occur around ${DateTime.now().plus({ + seconds: dt, + })}`, + ); + + if (this.knex.client.config.client.includes('sqlite3')) { + nextRun = this.knex.raw( + `max(datetime(next_run_start_at, ?), datetime('now'))`, + [`+${dt} seconds`], + ); + } else if (this.knex.client.config.client.includes('mysql')) { + nextRun = this.knex.raw( + `greatest(next_run_start_at + interval ${dt} second, now())`, + ); + } else { + nextRun = this.knex.raw( + `greatest(next_run_start_at + interval '${dt} seconds', now())`, + ); + } + } + + const rows = await this.knex(DB_TASKS_TABLE) + .where('id', '=', this.taskId) + .where('current_run_ticket', '=', ticket) + .update({ + next_run_start_at: nextRun, + current_run_ticket: this.knex.raw('null'), + current_run_started_at: this.knex.raw('null'), + current_run_expires_at: this.knex.raw('null'), + }); + + return rows === 1; + } + + private nextRunAtRaw(time: DateTime): Knex.Raw { + if (this.knex.client.config.client.includes('sqlite3')) { + return this.knex.raw('datetime(?)', [time.toISO()]); + } else if (this.knex.client.config.client.includes('mysql')) { + return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]); + } + return this.knex.raw(`?`, [time.toISO()]); + } +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/__testUtils__/createTestScopedSignal.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/__testUtils__/createTestScopedSignal.ts new file mode 100644 index 0000000000..6a497ea266 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/__testUtils__/createTestScopedSignal.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function createTestScopedSignal(): () => AbortSignal { + let testAbortController = new AbortController(); + + beforeEach(() => { + testAbortController = new AbortController(); + }); + afterEach(() => { + testAbortController.abort(); + }); + + return () => testAbortController.signal; +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts new file mode 100644 index 0000000000..83f7b80073 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts @@ -0,0 +1,158 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + SchedulerServiceTaskDescriptor, + SchedulerServiceTaskInvocationDefinition, + SchedulerServiceTaskRunner, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; +import { CronTime } from 'cron'; +import { Duration } from 'luxon'; +import { z } from 'zod'; + +/** + * Deals with the scheduling of distributed tasks, for a given plugin. + */ +export interface PluginTaskScheduler { + /** + * Manually triggers a task by ID. + * + * If the task doesn't exist, a NotFoundError is thrown. If the task is + * currently running, a ConflictError is thrown. + * + * @param id - The task ID + */ + triggerTask(id: string): Promise; + + /** + * Schedules a task function for recurring runs. + * + * @remarks + * + * The `scope` task field controls whether to use coordinated exclusive + * invocation across workers, or to just coordinate within the current worker. + * + * This convenience method performs both the scheduling and invocation in one + * go. + * + * @param task - The task definition + */ + scheduleTask( + task: SchedulerServiceTaskScheduleDefinition & + SchedulerServiceTaskInvocationDefinition, + ): Promise; + + /** + * Creates a scheduled but dormant recurring task, ready to be launched at a + * later time. + * + * @remarks + * + * This method is useful for pre-creating a schedule in outer code to be + * passed into an inner implementation, such that the outer code controls + * scheduling while inner code controls implementation. + * + * @param schedule - The task schedule + */ + createScheduledTaskRunner( + schedule: SchedulerServiceTaskScheduleDefinition, + ): SchedulerServiceTaskRunner; + + /** + * Returns all scheduled tasks registered to this scheduler. + * + * @remarks + * + * This method is useful for triggering tasks manually using the triggerTask + * functionality. Note that the returned tasks contain only tasks that have + * been initialized in this instance of the scheduler. + * + * @returns Scheduled tasks + */ + getScheduledTasks(): Promise; +} + +function isValidOptionalDurationString(d: string | undefined): boolean { + try { + return !d || Duration.fromISO(d).isValid; + } catch { + return false; + } +} + +function isValidCronFormat(c: string | undefined): boolean { + try { + if (!c) { + return false; + } + // parse cron format to ensure it's a valid format. + // eslint-disable-next-line no-new + new CronTime(c); + return true; + } catch { + return false; + } +} + +export const taskSettingsV1Schema = z.object({ + version: z.literal(1), + initialDelayDuration: z + .string() + .optional() + .refine(isValidOptionalDurationString, { + message: 'Invalid duration, expecting ISO Period', + }), + recurringAtMostEveryDuration: z + .string() + .refine(isValidOptionalDurationString, { + message: 'Invalid duration, expecting ISO Period', + }), + timeoutAfterDuration: z.string().refine(isValidOptionalDurationString, { + message: 'Invalid duration, expecting ISO Period', + }), +}); + +/** + * The properties that control a scheduled task (version 1). + */ +export type TaskSettingsV1 = z.infer; + +export const taskSettingsV2Schema = z.object({ + version: z.literal(2), + cadence: z + .string() + .refine(isValidCronFormat, { message: 'Invalid cron' }) + .or( + z.string().refine(isValidOptionalDurationString, { + message: 'Invalid duration, expecting ISO Period', + }), + ), + timeoutAfterDuration: z.string().refine(isValidOptionalDurationString, { + message: 'Invalid duration, expecting ISO Period', + }), + initialDelayDuration: z + .string() + .optional() + .refine(isValidOptionalDurationString, { + message: 'Invalid duration, expecting ISO Period', + }), +}); + +/** + * The properties that control a scheduled task (version 2). + */ +export type TaskSettingsV2 = z.infer; diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/util.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.test.ts new file mode 100644 index 0000000000..e2536abb88 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import knexFactory, { Knex } from 'knex'; +import { Duration } from 'luxon'; +import { delegateAbortController, nowPlus, sleep, validateId } from './util'; + +class KnexBuilder { + public build(client: string): Knex { + return knexFactory({ client, useNullAsDefault: true }); + } +} + +describe('util', () => { + describe('validateId', () => { + it.each(['a', 'a_b', 'ab123c_2', 'a!', 'A', 'a-b', 'a.b', '_a', 'a_'])( + 'accepts valid inputs, %p', + async input => { + expect(validateId(input)).toBeUndefined(); + }, + ); + + it.each(['', null, Symbol('a')])( + 'rejects invalid inputs, %p', + async input => { + expect(() => validateId(input as any)).toThrow(); + }, + ); + }); + + describe('sleep', () => { + it('finishes the wait as expected with no signal', async () => { + const ac = new AbortController(); + const start = Date.now(); + await sleep(Duration.fromObject({ seconds: 1 }), ac.signal); + expect(Date.now() - start).toBeGreaterThan(800); + }, 5_000); + + it('aborts properly on the signal', async () => { + const ac = new AbortController(); + const promise = sleep(Duration.fromObject({ seconds: 10 }), ac.signal); + ac.abort(); + await promise; + expect(true).toBe(true); + }, 1_000); + }); + + describe('delegateAbortController', () => { + it('inherits parent abort state', () => { + const parent = new AbortController(); + const child = delegateAbortController(parent.signal); + expect(parent.signal.aborted).toBe(false); + expect(child.signal.aborted).toBe(false); + parent.abort(); + expect(parent.signal.aborted).toBe(true); + expect(child.signal.aborted).toBe(true); + }); + + it('does not inherit from child to parent', () => { + const parent = new AbortController(); + const child = delegateAbortController(parent.signal); + expect(parent.signal.aborted).toBe(false); + expect(child.signal.aborted).toBe(false); + child.abort(); + expect(parent.signal.aborted).toBe(false); + expect(child.signal.aborted).toBe(true); + }); + }); + + describe('nowPlus', () => { + describe('without duration', () => { + const databases = [ + { client: 'sqlite3', expected: 'CURRENT_TIMESTAMP' }, + { client: 'mysql2', expected: 'CURRENT_TIMESTAMP' }, + { client: 'pg', expected: 'CURRENT_TIMESTAMP' }, + ]; + + it.each(databases)('for client $client', ({ client, expected }) => { + const knex = new KnexBuilder().build(client); + const result = nowPlus(undefined, knex); + + expect(result.toString()).toBe(expected); + }); + }); + describe('With duration', () => { + const databases = [ + { client: 'sqlite3', expected: "datetime('now', '20 seconds')" }, + { client: 'mysql2', expected: 'now() + interval 20 second' }, + { client: 'pg', expected: "now() + interval '20 seconds'" }, + ]; + it.each(databases)('for client $client', ({ client, expected }) => { + const duration = Duration.fromObject({ seconds: 20 }); + const knex = new KnexBuilder().build(client); + const result = nowPlus(duration, knex); + + expect(result.toString()).toBe(expected); + }); + }); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts new file mode 100644 index 0000000000..70d67a9fbe --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { Knex } from 'knex'; +import { DateTime, Duration } from 'luxon'; + +// Keep the IDs compatible with e.g. Prometheus labels +export function validateId(id: string) { + if (typeof id !== 'string' || !id.trim()) { + throw new InputError(`${id} is not a valid ID, expected non-empty string`); + } +} + +export function dbTime(t: Date | string): DateTime { + if (typeof t === 'string') { + return DateTime.fromSQL(t); + } + return DateTime.fromJSDate(t); +} + +export function nowPlus(duration: Duration | undefined, knex: Knex) { + const seconds = duration?.as('seconds') ?? 0; + if (!seconds) { + return knex.fn.now(); + } + + if (knex.client.config.client.includes('sqlite3')) { + return knex.raw(`datetime('now', ?)`, [`${seconds} seconds`]); + } + + if (knex.client.config.client.includes('mysql')) { + return knex.raw(`now() + interval ${seconds} second`); + } + + return knex.raw(`now() + interval '${seconds} seconds'`); +} + +/** + * Sleep for the given duration, but return sooner if the abort signal + * triggers. + * + * @param duration - The amount of time to sleep, at most + * @param abortSignal - An optional abort signal that short circuits the wait + */ +export async function sleep( + duration: Duration, + abortSignal?: AbortSignal, +): Promise { + if (abortSignal?.aborted) { + return; + } + + await new Promise(resolve => { + let timeoutHandle: NodeJS.Timeout | undefined = undefined; + + const done = () => { + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + abortSignal?.removeEventListener('abort', done); + resolve(); + }; + + timeoutHandle = setTimeout(done, duration.as('milliseconds')); + abortSignal?.addEventListener('abort', done); + }); +} + +/** + * Creates a new AbortController that, in addition to working as a regular + * standalone controller, also gets aborted if the given parent signal + * reaches aborted state. + * + * @param parent - The "parent" signal that can trigger the delegate + */ +export function delegateAbortController(parent?: AbortSignal): AbortController { + const delegate = new AbortController(); + + if (parent) { + if (parent.aborted) { + delegate.abort(); + } else { + const onParentAborted = () => { + delegate.abort(); + }; + + const onChildAborted = () => { + parent.removeEventListener('abort', onParentAborted); + }; + + parent.addEventListener('abort', onParentAborted, { once: true }); + delegate.signal.addEventListener('abort', onChildAborted, { once: true }); + } + } + + return delegate; +} diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts new file mode 100644 index 0000000000..7b889b3a0e --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { coreServices } from '@backstage/backend-plugin-api'; +import { ServiceFactoryTester } from '@backstage/backend-test-utils'; +import { schedulerServiceFactory } from './schedulerServiceFactory'; + +describe('schedulerFactory', () => { + it('creates sidecar database features', async () => { + const tester = ServiceFactoryTester.from(schedulerServiceFactory()); + + const scheduler = await tester.get(); + await scheduler.scheduleTask({ + id: 'task1', + timeout: { seconds: 1 }, + frequency: { seconds: 1 }, + fn: async () => {}, + }); + + const database = await tester.getService(coreServices.database); + + const client = await database.getClient(); + await expect( + client.from('backstage_backend_tasks__tasks').count(), + ).resolves.toEqual([{ 'count(*)': 1 }]); + await expect( + client.from('backstage_backend_tasks__knex_migrations').count(), + ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]); + await expect( + client.from('backstage_backend_tasks__knex_migrations_lock').count(), + ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts new file mode 100644 index 0000000000..55c0d58e7b --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts @@ -0,0 +1,42 @@ +/* + * 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 { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { TaskScheduler } from './lib/TaskScheduler'; + +/** + * The default service factory for {@link @backstage/backend-plugin-api#coreServices.scheduler}. + * + * @public + */ +export const schedulerServiceFactory = createServiceFactory({ + service: coreServices.scheduler, + deps: { + plugin: coreServices.pluginMetadata, + database: coreServices.database, + logger: coreServices.logger, + }, + async factory({ plugin, database, logger }) { + return TaskScheduler.forPlugin({ + pluginId: plugin.getId(), + databaseManager: database, + logger, + }); + }, +}); diff --git a/packages/backend-defaults/src/setupTests.ts b/packages/backend-defaults/src/setupTests.ts new file mode 100644 index 0000000000..76619a2542 --- /dev/null +++ b/packages/backend-defaults/src/setupTests.ts @@ -0,0 +1,25 @@ +/* + * 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 { TestDatabases } from '@backstage/backend-test-utils'; +import { Settings } from 'luxon'; + +// TS still thinks that methods can return null / placeholders, but we still want to throw as soon as possible when things go wrong +Settings.throwOnInvalid = true; + +TestDatabases.setDefaults({ + ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], +}); diff --git a/packages/backend-plugin-api/api-report-scheduler.md b/packages/backend-plugin-api/api-report-scheduler.md deleted file mode 100644 index 480c716025..0000000000 --- a/packages/backend-plugin-api/api-report-scheduler.md +++ /dev/null @@ -1,79 +0,0 @@ -## API Report File for "@backstage/backend-plugin-api" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts -import { Config } from '@backstage/config'; -import { Duration } from 'luxon'; -import { HumanDuration } from '@backstage/types'; -import { JsonObject } from '@backstage/types'; - -// @public -export function readTaskScheduleDefinitionFromConfig( - config: Config, -): TaskScheduleDefinition; - -// @public -export interface SchedulerService { - createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner; - getScheduledTasks(): Promise; - scheduleTask( - task: TaskScheduleDefinition & TaskInvocationDefinition, - ): Promise; - triggerTask(id: string): Promise; -} - -// @public -export type TaskDescriptor = { - id: string; - scope: 'global' | 'local'; - settings: { - version: number; - } & JsonObject; -}; - -// @public -export type TaskFunction = - | ((abortSignal: AbortSignal) => void | Promise) - | (() => void | Promise); - -// @public -export interface TaskInvocationDefinition { - fn: TaskFunction; - id: string; - signal?: AbortSignal; -} - -// @public -export interface TaskRunner { - run(task: TaskInvocationDefinition): Promise; -} - -// @public -export interface TaskScheduleDefinition { - frequency: - | { - cron: string; - } - | Duration - | HumanDuration; - initialDelay?: Duration | HumanDuration; - scope?: 'global' | 'local'; - timeout: Duration | HumanDuration; -} - -// @public -export interface TaskScheduleDefinitionConfig { - frequency: - | { - cron: string; - } - | string - | HumanDuration; - initialDelay?: string | HumanDuration; - scope?: 'global' | 'local'; - timeout: string | HumanDuration; -} - -// (No @packageDocumentation comment for this package) -``` diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 4e03da15b3..1ba2de768a 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -8,7 +8,9 @@ import { AuthorizePermissionRequest } from '@backstage/plugin-permission-common'; import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; +import { Duration } from 'luxon'; import { Handler } from 'express'; +import { HumanDuration } from '@backstage/types'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; @@ -20,7 +22,6 @@ import { QueryPermissionResponse } from '@backstage/plugin-permission-common'; import { Readable } from 'stream'; import { Request as Request_2 } from 'express'; import { Response as Response_2 } from 'express'; -import { SchedulerService as SchedulerService_2 } from '@backstage/backend-plugin-api/scheduler'; // @public (undocumented) export interface AuthService { @@ -202,7 +203,7 @@ export namespace coreServices { const rootHttpRouter: ServiceRef; const rootLifecycle: ServiceRef; const rootLogger: ServiceRef; - const scheduler: ServiceRef; + const scheduler: ServiceRef; const tokenManager: ServiceRef; const urlReader: ServiceRef; const identity: ServiceRef; @@ -450,6 +451,11 @@ export interface PluginServiceFactoryConfig< service: ServiceRef; } +// @public +export function readSchedulerServiceTaskScheduleDefinitionFromConfig( + config: Config, +): SchedulerServiceTaskScheduleDefinition; + // @public export type ReadTreeOptions = { filter?( @@ -536,8 +542,70 @@ export interface RootServiceFactoryConfig< service: ServiceRef; } -// @public @deprecated (undocumented) -export type SchedulerService = SchedulerService_2; +// @public +export interface SchedulerService { + createScheduledTaskRunner( + schedule: SchedulerServiceTaskScheduleDefinition, + ): SchedulerServiceTaskRunner; + getScheduledTasks(): Promise; + scheduleTask( + task: SchedulerServiceTaskScheduleDefinition & + SchedulerServiceTaskInvocationDefinition, + ): Promise; + triggerTask(id: string): Promise; +} + +// @public +export type SchedulerServiceTaskDescriptor = { + id: string; + scope: 'global' | 'local'; + settings: { + version: number; + } & JsonObject; +}; + +// @public +export type SchedulerServiceTaskFunction = + | ((abortSignal: AbortSignal) => void | Promise) + | (() => void | Promise); + +// @public +export interface SchedulerServiceTaskInvocationDefinition { + fn: SchedulerServiceTaskFunction; + id: string; + signal?: AbortSignal; +} + +// @public +export interface SchedulerServiceTaskRunner { + run(task: SchedulerServiceTaskInvocationDefinition): Promise; +} + +// @public +export interface SchedulerServiceTaskScheduleDefinition { + frequency: + | { + cron: string; + } + | Duration + | HumanDuration; + initialDelay?: Duration | HumanDuration; + scope?: 'global' | 'local'; + timeout: Duration | HumanDuration; +} + +// @public +export interface SchedulerServiceTaskScheduleDefinitionConfig { + frequency: + | { + cron: string; + } + | string + | HumanDuration; + initialDelay?: string | HumanDuration; + scope?: 'global' | 'local'; + timeout: string | HumanDuration; +} // @public export type SearchOptions = { diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 3ffbb5b967..73668bad35 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -20,7 +20,6 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", - "./scheduler": "./src/entrypoints/scheduler/index.ts", "./alpha": "./src/alpha.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" @@ -29,9 +28,6 @@ "types": "src/index.ts", "typesVersions": { "*": { - "scheduler": [ - "src/entrypoints/scheduler/index.ts" - ], "alpha": [ "src/alpha.ts" ], diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts deleted file mode 100644 index 8053936263..0000000000 --- a/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Config, readDurationFromConfig } from '@backstage/config'; -import { HumanDuration } from '@backstage/types'; -import { TaskScheduleDefinition } from './types'; -import { Duration } from 'luxon'; - -function readDuration(config: Config, key: string): Duration | HumanDuration { - if (typeof config.get(key) === 'string') { - const value = config.getString(key); - const duration = Duration.fromISO(value); - if (!duration.isValid) { - throw new Error(`Invalid duration: ${value}`); - } - return duration; - } - - return readDurationFromConfig(config, { key }); -} - -function readCronOrDuration( - config: Config, - key: string, -): { cron: string } | Duration | HumanDuration { - const value = config.get(key); - if (typeof value === 'object' && (value as { cron?: string }).cron) { - return value as { cron: string }; - } - - return readDuration(config, key); -} - -/** - * Reads a TaskScheduleDefinition from a Config. - * Expects the config not to be the root config, - * but the config for the definition. - * - * @param config - config for a TaskScheduleDefinition. - * @public - */ -export function readTaskScheduleDefinitionFromConfig( - config: Config, -): TaskScheduleDefinition { - const frequency = readCronOrDuration(config, 'frequency'); - const timeout = readDuration(config, 'timeout'); - - const initialDelay = config.has('initialDelay') - ? readDuration(config, 'initialDelay') - : undefined; - - const scope = config.getOptionalString('scope'); - if (scope && !['global', 'local'].includes(scope)) { - throw new Error( - `Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: ${scope}`, - ); - } - - return { - frequency, - timeout, - initialDelay, - scope: scope as 'global' | 'local' | undefined, - }; -} diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts index 342b33dd2e..c2e2a13a89 100644 --- a/packages/backend-plugin-api/src/index.ts +++ b/packages/backend-plugin-api/src/index.ts @@ -24,4 +24,3 @@ export * from './services'; export type { BackendFeature } from './types'; export * from './paths'; export * from './wiring'; -export * from './deprecated'; diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-plugin-api/src/services/definitions/SchedulerService.test.ts similarity index 76% rename from packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts rename to packages/backend-plugin-api/src/services/definitions/SchedulerService.test.ts index c52d59b016..7b20487af4 100644 --- a/packages/backend-plugin-api/src/entrypoints/scheduler/readTaskScheduleDefinitionFromConfig.test.ts +++ b/packages/backend-plugin-api/src/services/definitions/SchedulerService.test.ts @@ -17,9 +17,9 @@ import { ConfigReader } from '@backstage/config'; import { HumanDuration } from '@backstage/types'; import { Duration } from 'luxon'; -import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from './SchedulerService'; -describe('readTaskScheduleDefinitionFromConfig', () => { +describe('readSchedulerServiceTaskScheduleDefinitionFromConfig', () => { it('all valid values', () => { const config = new ConfigReader({ frequency: { @@ -32,7 +32,7 @@ describe('readTaskScheduleDefinitionFromConfig', () => { scope: 'global', }); - const result = readTaskScheduleDefinitionFromConfig(config); + const result = readSchedulerServiceTaskScheduleDefinitionFromConfig(config); expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); expect(result.timeout).toEqual(Duration.fromISO('PT3M')); @@ -48,7 +48,7 @@ describe('readTaskScheduleDefinitionFromConfig', () => { timeout: 'PT3M', }); - const result = readTaskScheduleDefinitionFromConfig(config); + const result = readSchedulerServiceTaskScheduleDefinitionFromConfig(config); expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); expect(result.timeout).toEqual(Duration.fromISO('PT3M')); @@ -61,9 +61,9 @@ describe('readTaskScheduleDefinitionFromConfig', () => { timeout: 'PT3M', }); - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - "Missing required config value at 'frequency'", - ); + expect(() => + readSchedulerServiceTaskScheduleDefinitionFromConfig(config), + ).toThrow("Missing required config value at 'frequency'"); }); it('fail without required timeout', () => { @@ -71,9 +71,9 @@ describe('readTaskScheduleDefinitionFromConfig', () => { frequency: 'PT30M', }); - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( - "Missing required config value at 'timeout'", - ); + expect(() => + readSchedulerServiceTaskScheduleDefinitionFromConfig(config), + ).toThrow("Missing required config value at 'timeout'"); }); it('invalid frequency key', () => { @@ -84,7 +84,9 @@ describe('readTaskScheduleDefinitionFromConfig', () => { timeout: 'PT3M', }); - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + expect(() => + readSchedulerServiceTaskScheduleDefinitionFromConfig(config), + ).toThrow( "Failed to read duration from config at 'frequency', Error: Needs one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'", ); }); @@ -97,7 +99,9 @@ describe('readTaskScheduleDefinitionFromConfig', () => { timeout: 'PT3M', }); - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + expect(() => + readSchedulerServiceTaskScheduleDefinitionFromConfig(config), + ).toThrow( "Failed to read duration from config, Error: Unable to convert config value for key 'frequency.minutes' in 'mock-config' to a number", ); }); @@ -111,7 +115,9 @@ describe('readTaskScheduleDefinitionFromConfig', () => { timeout: 'PT3M', }); - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + expect(() => + readSchedulerServiceTaskScheduleDefinitionFromConfig(config), + ).toThrow( "Failed to read duration from config at 'frequency', Error: Unknown property 'invalid'; expected one or more of 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds'", ); }); @@ -125,7 +131,9 @@ describe('readTaskScheduleDefinitionFromConfig', () => { scope: 'invalid', }); - expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + expect(() => + readSchedulerServiceTaskScheduleDefinitionFromConfig(config), + ).toThrow( 'Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: invalid', ); }); diff --git a/packages/backend-plugin-api/src/entrypoints/scheduler/types.ts b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts similarity index 81% rename from packages/backend-plugin-api/src/entrypoints/scheduler/types.ts rename to packages/backend-plugin-api/src/services/definitions/SchedulerService.ts index 5b14e52fcb..ad0fc1cf3a 100644 --- a/packages/backend-plugin-api/src/entrypoints/scheduler/types.ts +++ b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Config, readDurationFromConfig } from '@backstage/config'; import { HumanDuration, JsonObject } from '@backstage/types'; import { Duration } from 'luxon'; @@ -25,7 +26,7 @@ import { Duration } from 'luxon'; * * @public */ -export type TaskFunction = +export type SchedulerServiceTaskFunction = | ((abortSignal: AbortSignal) => void | Promise) | (() => void | Promise); @@ -34,7 +35,7 @@ export type TaskFunction = * * @public */ -export type TaskDescriptor = { +export type SchedulerServiceTaskDescriptor = { /** * The unique identifier of the task. */ @@ -56,7 +57,7 @@ export type TaskDescriptor = { * * @public */ -export interface TaskScheduleDefinition { +export interface SchedulerServiceTaskScheduleDefinition { /** * How often you want the task to run. The system does its best to avoid * overlapping invocations. @@ -148,12 +149,12 @@ export interface TaskScheduleDefinition { } /** - * Config options for {@link TaskScheduleDefinition} + * Config options for {@link SchedulerServiceTaskScheduleDefinition} * that control the scheduling of a task. * * @public */ -export interface TaskScheduleDefinitionConfig { +export interface SchedulerServiceTaskScheduleDefinitionConfig { /** * How often you want the task to run. The system does its best to avoid * overlapping invocations. @@ -249,7 +250,7 @@ export interface TaskScheduleDefinitionConfig { * * @public */ -export interface TaskInvocationDefinition { +export interface SchedulerServiceTaskInvocationDefinition { /** * A unique ID (within the scope of the plugin) for the task. */ @@ -258,7 +259,7 @@ export interface TaskInvocationDefinition { /** * The actual task function to be invoked regularly. */ - fn: TaskFunction; + fn: SchedulerServiceTaskFunction; /** * An abort signal that, when triggered, will stop the recurring execution of @@ -272,13 +273,13 @@ export interface TaskInvocationDefinition { * * @public */ -export interface TaskRunner { +export interface SchedulerServiceTaskRunner { /** * Takes the schedule and executes an actual task using it. * * @param task - The actual runtime properties of the task */ - run(task: TaskInvocationDefinition): Promise; + run(task: SchedulerServiceTaskInvocationDefinition): Promise; } /** @@ -311,7 +312,8 @@ export interface SchedulerService { * @param task - The task definition */ scheduleTask( - task: TaskScheduleDefinition & TaskInvocationDefinition, + task: SchedulerServiceTaskScheduleDefinition & + SchedulerServiceTaskInvocationDefinition, ): Promise; /** @@ -326,7 +328,9 @@ export interface SchedulerService { * * @param schedule - The task schedule */ - createScheduledTaskRunner(schedule: TaskScheduleDefinition): TaskRunner; + createScheduledTaskRunner( + schedule: SchedulerServiceTaskScheduleDefinition, + ): SchedulerServiceTaskRunner; /** * Returns all scheduled tasks registered to this scheduler. @@ -339,5 +343,62 @@ export interface SchedulerService { * * @returns Scheduled tasks */ - getScheduledTasks(): Promise; + getScheduledTasks(): Promise; +} + +function readDuration(config: Config, key: string): Duration | HumanDuration { + if (typeof config.get(key) === 'string') { + const value = config.getString(key); + const duration = Duration.fromISO(value); + if (!duration.isValid) { + throw new Error(`Invalid duration: ${value}`); + } + return duration; + } + + return readDurationFromConfig(config, { key }); +} + +function readCronOrDuration( + config: Config, + key: string, +): { cron: string } | Duration | HumanDuration { + const value = config.get(key); + if (typeof value === 'object' && (value as { cron?: string }).cron) { + return value as { cron: string }; + } + + return readDuration(config, key); +} + +/** + * Reads a {@link SchedulerServiceTaskScheduleDefinition} from config. Expects + * the config not to be the root config, but the config for the definition. + * + * @param config - config for a TaskScheduleDefinition. + * @public + */ +export function readSchedulerServiceTaskScheduleDefinitionFromConfig( + config: Config, +): SchedulerServiceTaskScheduleDefinition { + const frequency = readCronOrDuration(config, 'frequency'); + const timeout = readDuration(config, 'timeout'); + + const initialDelay = config.has('initialDelay') + ? readDuration(config, 'initialDelay') + : undefined; + + const scope = config.getOptionalString('scope'); + if (scope && !['global', 'local'].includes(scope)) { + throw new Error( + `Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: ${scope}`, + ); + } + + return { + frequency, + timeout, + initialDelay, + scope: scope as 'global' | 'local' | undefined, + }; } diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index 09f313af57..c760afc7b4 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -165,7 +165,7 @@ export namespace coreServices { * @public */ export const scheduler = createServiceRef< - import('@backstage/backend-plugin-api/scheduler').SchedulerService + import('./SchedulerService').SchedulerService >({ id: 'core.scheduler' }); /** diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index bd436cf899..5739add90c 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -52,6 +52,16 @@ export type { PluginMetadataService } from './PluginMetadataService'; export type { RootHttpRouterService } from './RootHttpRouterService'; export type { RootLifecycleService } from './RootLifecycleService'; export type { RootLoggerService } from './RootLoggerService'; +export { readSchedulerServiceTaskScheduleDefinitionFromConfig } from './SchedulerService'; +export type { + SchedulerService, + SchedulerServiceTaskDescriptor, + SchedulerServiceTaskFunction, + SchedulerServiceTaskInvocationDefinition, + SchedulerServiceTaskRunner, + SchedulerServiceTaskScheduleDefinition, + SchedulerServiceTaskScheduleDefinitionConfig, +} from './SchedulerService'; export type { TokenManagerService } from './TokenManagerService'; export type { ReadTreeOptions, diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts index ac9039adcd..daff280020 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts @@ -33,7 +33,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; * Deals with the scheduling of distributed tasks. * * @public - * @deprecated Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead. The new default implementation of this service lives in `@backstage/backend-defaults/scheduler`. + * @deprecated Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead. */ export class TaskScheduler { static fromConfig( diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts index 2e761f2f94..5a173f246b 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts @@ -51,7 +51,7 @@ function readCronOrDuration( * * @param config - config for a TaskScheduleDefinition. * @public - * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead */ export function readTaskScheduleDefinitionFromConfig( config: Config, diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index 3d49e19cc1..955909e040 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -26,7 +26,7 @@ import { z } from 'zod'; * processing should abort and return as quickly as possible. * * @public - * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead */ export type TaskFunction = | ((abortSignal: AbortSignal) => void | Promise) @@ -36,7 +36,7 @@ export type TaskFunction = * A semi-opaque type to describe an actively scheduled task. * * @public - * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead */ export type TaskDescriptor = { /** @@ -59,7 +59,7 @@ export type TaskDescriptor = { * Options that control the scheduling of a task. * * @public - * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please import `SchedulerServiceTaskScheduleDefinition` from `@backstage/backend-plugin-api` instead */ export interface TaskScheduleDefinition { /** @@ -157,7 +157,7 @@ export interface TaskScheduleDefinition { * that control the scheduling of a task. * * @public - * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please import `SchedulerServiceTaskDefinitionConfig` from `@backstage/backend-plugin-api` instead */ export interface TaskScheduleDefinitionConfig { /** @@ -254,7 +254,7 @@ export interface TaskScheduleDefinitionConfig { * Options that apply to the invocation of a given task. * * @public - * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead */ export interface TaskInvocationDefinition { /** @@ -278,7 +278,7 @@ export interface TaskInvocationDefinition { * A previously prepared task schedule, ready to be invoked. * * @public - * @deprecated Please import from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead */ export interface TaskRunner { /** @@ -293,7 +293,7 @@ export interface TaskRunner { * Deals with the scheduling of distributed tasks, for a given plugin. * * @public - * @deprecated Please use `SchedulerService` from `@backstage/backend-plugin-api/scheduler` instead + * @deprecated Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`) */ export interface PluginTaskScheduler { /** diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 6e9cdc9459..4f6eb47243 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -34,7 +34,7 @@ import { RootHttpRouterFactoryOptions } from '@backstage/backend-app-api'; import { RootHttpRouterService } from '@backstage/backend-plugin-api'; import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { SchedulerService } from '@backstage/backend-plugin-api/scheduler'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; diff --git a/yarn.lock b/yarn.lock index 3bdc15bbb7..23620ac274 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3433,7 +3433,17 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/types": "workspace:^" + "@opentelemetry/api": ^1.3.0 + cron: ^3.0.0 + knex: ^3.0.0 + lodash: ^4.17.21 + luxon: ^3.0.0 + uuid: ^9.0.0 + wait-for-expect: ^3.0.2 + zod: ^3.22.4 languageName: unknown linkType: soft From 6551b3d4a97dba29ca0da9fa7cdfe4e4572e6735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 30 Apr 2024 13:48:39 +0200 Subject: [PATCH 443/567] changesets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/lucky-taxis-rule.md | 5 +++++ .changeset/new-numbers-hug.md | 5 +++++ .changeset/rude-kings-press.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .changeset/lucky-taxis-rule.md create mode 100644 .changeset/new-numbers-hug.md create mode 100644 .changeset/rude-kings-press.md diff --git a/.changeset/lucky-taxis-rule.md b/.changeset/lucky-taxis-rule.md new file mode 100644 index 0000000000..b222f8e339 --- /dev/null +++ b/.changeset/lucky-taxis-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Added the `schedulerServiceFactory` and its implementation, migrated over from `@backstage/backend-app-api` diff --git a/.changeset/new-numbers-hug.md b/.changeset/new-numbers-hug.md new file mode 100644 index 0000000000..e4e35a2825 --- /dev/null +++ b/.changeset/new-numbers-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +Moved the declaration of the `SchedulerService` here, along with prefixed versions of all of the types it depends on, from `@backstage/backend-tasks` diff --git a/.changeset/rude-kings-press.md b/.changeset/rude-kings-press.md new file mode 100644 index 0000000000..148d57de14 --- /dev/null +++ b/.changeset/rude-kings-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Deprecated `schedulerServiceFactory`, which should now instead be imported from `@backstage/backend-defaults/scheduler` instead From ea2f38c51d51fa3b466c98f3c35dc9ebe22e9169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 30 Apr 2024 14:09:53 +0200 Subject: [PATCH 444/567] remove backend-tasks dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- packages/backend-plugin-api/package.json | 1 - yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 73668bad35..47187bf5af 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -52,7 +52,6 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-tasks": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 23620ac274..6c1664f767 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3516,7 +3516,6 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/backend-plugin-api@workspace:packages/backend-plugin-api" dependencies: - "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" From 4711ca38d69cc9d3486f72a3b59d6f5be9043e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 30 Apr 2024 14:31:26 +0200 Subject: [PATCH 445/567] remove the unneeded copy of the task scheduler interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- packages/backend-defaults/package.json | 46 ++++++------- .../scheduler/lib/PluginTaskSchedulerImpl.ts | 7 +- .../scheduler/lib/TaskScheduler.ts | 8 +-- .../src/entrypoints/scheduler/lib/types.ts | 68 ------------------- 4 files changed, 30 insertions(+), 99 deletions(-) diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 742a1f99c1..05c7ea511c 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -2,21 +2,29 @@ "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", "version": "0.2.18", - "main": "src/index.ts", - "types": "src/index.ts", - "publishConfig": { - "access": "public", - "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" - }, "backstage": { "role": "node-library" }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-defaults" + }, + "license": "Apache-2.0", "exports": { ".": "./src/index.ts", "./scheduler": "./src/entrypoints/scheduler/index.ts", "./package.json": "./package.json" }, + "main": "src/index.ts", + "types": "src/index.ts", "typesVersions": { "*": { "scheduler": [ @@ -27,24 +35,18 @@ ] } }, - "homepage": "https://backstage.io", - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "packages/backend-defaults" - }, - "keywords": [ - "backstage" + "files": [ + "dist", + "migrations" ], - "license": "Apache-2.0", "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-app-api": "workspace:^", @@ -66,9 +68,5 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" - }, - "files": [ - "dist", - "migrations" - ] + } } diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts index ed14d75db2..62b36e024c 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; import { + LoggerService, + SchedulerService, SchedulerServiceTaskDescriptor, SchedulerServiceTaskFunction, SchedulerServiceTaskInvocationDefinition, @@ -27,13 +28,13 @@ import { Knex } from 'knex'; import { Duration } from 'luxon'; import { LocalTaskWorker } from './LocalTaskWorker'; import { TaskWorker } from './TaskWorker'; -import { PluginTaskScheduler, TaskSettingsV2 } from './types'; +import { TaskSettingsV2 } from './types'; import { validateId } from './util'; /** * Implements the actual task management. */ -export class PluginTaskSchedulerImpl implements PluginTaskScheduler { +export class PluginTaskSchedulerImpl implements SchedulerService { private readonly localTasksById = new Map(); private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = []; diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts index 0757d680ae..21a19ce700 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskScheduler.ts @@ -23,13 +23,13 @@ import { import { LoggerService, RootConfigService, + SchedulerService, } from '@backstage/backend-plugin-api'; import { once } from 'lodash'; import { Duration } from 'luxon'; import { migrateBackendTasks } from '../database/migrateBackendTasks'; import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl'; import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor'; -import { PluginTaskScheduler } from './types'; /** * Deals with the scheduling of distributed tasks. @@ -59,9 +59,9 @@ export class TaskScheduler { * Instantiates a task manager instance for the given plugin. * * @param pluginId - The unique ID of the plugin, for example "catalog" - * @returns A {@link PluginTaskScheduler} instance + * @returns A {@link SchedulerService} instance */ - forPlugin(pluginId: string): PluginTaskScheduler { + forPlugin(pluginId: string): SchedulerService { return TaskScheduler.forPlugin({ pluginId, databaseManager: this.databaseManager.forPlugin(pluginId), @@ -73,7 +73,7 @@ export class TaskScheduler { pluginId: string; databaseManager: PluginDatabaseManager; logger: LoggerService; - }): PluginTaskScheduler { + }): SchedulerService { const databaseFactory = once(async () => { const knex = await opts.databaseManager.getClient(); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts index 83f7b80073..61e6c1d9a8 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts @@ -14,78 +14,10 @@ * limitations under the License. */ -import { - SchedulerServiceTaskDescriptor, - SchedulerServiceTaskInvocationDefinition, - SchedulerServiceTaskRunner, - SchedulerServiceTaskScheduleDefinition, -} from '@backstage/backend-plugin-api'; import { CronTime } from 'cron'; import { Duration } from 'luxon'; import { z } from 'zod'; -/** - * Deals with the scheduling of distributed tasks, for a given plugin. - */ -export interface PluginTaskScheduler { - /** - * Manually triggers a task by ID. - * - * If the task doesn't exist, a NotFoundError is thrown. If the task is - * currently running, a ConflictError is thrown. - * - * @param id - The task ID - */ - triggerTask(id: string): Promise; - - /** - * Schedules a task function for recurring runs. - * - * @remarks - * - * The `scope` task field controls whether to use coordinated exclusive - * invocation across workers, or to just coordinate within the current worker. - * - * This convenience method performs both the scheduling and invocation in one - * go. - * - * @param task - The task definition - */ - scheduleTask( - task: SchedulerServiceTaskScheduleDefinition & - SchedulerServiceTaskInvocationDefinition, - ): Promise; - - /** - * Creates a scheduled but dormant recurring task, ready to be launched at a - * later time. - * - * @remarks - * - * This method is useful for pre-creating a schedule in outer code to be - * passed into an inner implementation, such that the outer code controls - * scheduling while inner code controls implementation. - * - * @param schedule - The task schedule - */ - createScheduledTaskRunner( - schedule: SchedulerServiceTaskScheduleDefinition, - ): SchedulerServiceTaskRunner; - - /** - * Returns all scheduled tasks registered to this scheduler. - * - * @remarks - * - * This method is useful for triggering tasks manually using the triggerTask - * functionality. Note that the returned tasks contain only tasks that have - * been initialized in this instance of the scheduler. - * - * @returns Scheduled tasks - */ - getScheduledTasks(): Promise; -} - function isValidOptionalDurationString(d: string | undefined): boolean { try { return !d || Duration.fromISO(d).isValid; From ddfd6608c5fc366dff545b5871c6a34e8bd8981d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 1 May 2024 17:27:25 +0200 Subject: [PATCH 446/567] luxon types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- packages/backend-plugin-api/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 47187bf5af..4569f205f9 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -59,6 +59,7 @@ "@backstage/plugin-permission-common": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "^4.17.6", + "@types/luxon": "^3.0.0", "express": "^4.17.1", "knex": "^3.0.0", "luxon": "^3.0.0" diff --git a/yarn.lock b/yarn.lock index 6c1664f767..c143baf388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3525,6 +3525,7 @@ __metadata: "@backstage/plugin-permission-common": "workspace:^" "@backstage/types": "workspace:^" "@types/express": ^4.17.6 + "@types/luxon": ^3.0.0 express: ^4.17.1 knex: ^3.0.0 luxon: ^3.0.0 From b0210ec5b6eee6d9f26161db49ca591414d0cedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 24 Apr 2024 15:15:10 +0200 Subject: [PATCH 447/567] move over most trivial services to backend-defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/lucky-taxis-rule.md | 6 +- .changeset/rude-kings-press.md | 5 +- packages/backend-app-api/api-report.md | 26 +- .../cache/cacheServiceFactory.ts | 5 +- .../config/rootConfigServiceFactory.ts | 10 +- .../database/databaseServiceFactory.ts | 5 +- .../discovery/HostDiscovery.ts | 1 + .../discovery/discoveryServiceFactory.ts | 5 +- .../identity/identityServiceFactory.ts | 12 +- .../lifecycle/lifecycleServiceFactory.ts | 7 +- .../permissions/permissionsServiceFactory.ts | 5 +- .../rootLifecycleServiceFactory.ts | 6 +- .../tokenManagerServiceFactory.ts | 5 +- .../urlReader/urlReaderServiceFactory.ts | 5 +- .../src/wiring/BackendInitializer.ts | 31 ++- packages/backend-defaults/api-report-cache.md | 13 + .../backend-defaults/api-report-database.md | 16 ++ .../backend-defaults/api-report-discovery.md | 31 +++ .../backend-defaults/api-report-lifecycle.md | 16 ++ .../api-report-permissions.md | 16 ++ .../backend-defaults/api-report-rootConfig.md | 24 ++ .../api-report-rootLifecycle.md | 16 ++ .../backend-defaults/api-report-urlReader.md | 13 + packages/backend-defaults/config.d.ts | 39 +++ packages/backend-defaults/package.json | 39 ++- .../backend-defaults/src/CreateBackend.ts | 26 +- .../entrypoints/cache/cacheServiceFactory.ts | 38 +++ .../src/entrypoints/cache/index.ts | 17 ++ .../database/databaseServiceFactory.ts | 51 ++++ .../src/entrypoints/database/index.ts | 17 ++ .../discovery/HostDiscovery.test.ts | 257 ++++++++++++++++++ .../entrypoints/discovery/HostDiscovery.ts | 132 +++++++++ .../discovery/discoveryServiceFactory.ts | 32 +++ .../src/entrypoints/discovery/index.ts | 18 ++ .../src/entrypoints/lifecycle/index.ts | 17 ++ .../lifecycle/lifecycleServiceFactory.ts | 106 ++++++++ .../src/entrypoints/permissions/index.ts | 17 ++ .../permissions/permissionsServiceFactory.ts | 41 +++ .../src/entrypoints/rootConfig/index.ts | 18 ++ .../rootConfig/rootConfigServiceFactory.ts | 59 ++++ .../src/entrypoints/rootLifecycle/index.ts | 17 ++ .../rootLifecycleServiceFactory.test.ts | 60 ++++ .../rootLifecycleServiceFactory.ts | 120 ++++++++ .../src/entrypoints/urlReader/index.ts | 17 ++ .../urlReader/urlReaderServiceFactory.ts | 36 +++ packages/backend-plugin-api/api-report.md | 6 +- .../src/services/definitions/coreServices.ts | 2 + yarn.lock | 3 + 48 files changed, 1412 insertions(+), 52 deletions(-) create mode 100644 packages/backend-defaults/api-report-cache.md create mode 100644 packages/backend-defaults/api-report-database.md create mode 100644 packages/backend-defaults/api-report-discovery.md create mode 100644 packages/backend-defaults/api-report-lifecycle.md create mode 100644 packages/backend-defaults/api-report-permissions.md create mode 100644 packages/backend-defaults/api-report-rootConfig.md create mode 100644 packages/backend-defaults/api-report-rootLifecycle.md create mode 100644 packages/backend-defaults/api-report-urlReader.md create mode 100644 packages/backend-defaults/config.d.ts create mode 100644 packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts create mode 100644 packages/backend-defaults/src/entrypoints/cache/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts create mode 100644 packages/backend-defaults/src/entrypoints/database/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts create mode 100644 packages/backend-defaults/src/entrypoints/discovery/discoveryServiceFactory.ts create mode 100644 packages/backend-defaults/src/entrypoints/discovery/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/lifecycle/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/lifecycle/lifecycleServiceFactory.ts create mode 100644 packages/backend-defaults/src/entrypoints/permissions/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/permissions/permissionsServiceFactory.ts create mode 100644 packages/backend-defaults/src/entrypoints/rootConfig/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts create mode 100644 packages/backend-defaults/src/entrypoints/rootLifecycle/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.test.ts create mode 100644 packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.ts create mode 100644 packages/backend-defaults/src/entrypoints/urlReader/index.ts create mode 100644 packages/backend-defaults/src/entrypoints/urlReader/urlReaderServiceFactory.ts diff --git a/.changeset/lucky-taxis-rule.md b/.changeset/lucky-taxis-rule.md index b222f8e339..eacbaaefbe 100644 --- a/.changeset/lucky-taxis-rule.md +++ b/.changeset/lucky-taxis-rule.md @@ -2,4 +2,8 @@ '@backstage/backend-defaults': patch --- -Added the `schedulerServiceFactory` and its implementation, migrated over from `@backstage/backend-app-api` +Added core service factories and implementations from +`@backstage/backend-app-api`. They are now available as subpath exports, e.g. +`@backstage/backend-defaults/scheduler` is where the service factory and default +implementation of `coreServices.scheduler` now lives. They have been marked as +deprecated in their old locations. diff --git a/.changeset/rude-kings-press.md b/.changeset/rude-kings-press.md index 148d57de14..5c10753f8a 100644 --- a/.changeset/rude-kings-press.md +++ b/.changeset/rude-kings-press.md @@ -2,4 +2,7 @@ '@backstage/backend-app-api': patch --- -Deprecated `schedulerServiceFactory`, which should now instead be imported from `@backstage/backend-defaults/scheduler` instead +Deprecated core service factories and implementations and moved them over to +subpath exports on `@backstage/backend-defaults` instead. E.g. +`@backstage/backend-defaults/scheduler` is where the service factory and default +implementation of `coreServices.scheduler` now lives. diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 789873e53c..49965f4c46 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -65,7 +65,7 @@ export interface Backend { stop(): Promise; } -// @public (undocumented) +// @public @deprecated (undocumented) export const cacheServiceFactory: () => ServiceFactory; // @public (undocumented) @@ -100,7 +100,7 @@ export interface CreateSpecializedBackendOptions { defaultServiceFactories: ServiceFactoryOrFunction[]; } -// @public (undocumented) +// @public @deprecated (undocumented) export const databaseServiceFactory: () => ServiceFactory< PluginDatabaseManager, 'plugin' @@ -121,7 +121,7 @@ export interface DefaultRootHttpRouterOptions { indexPath?: string | false; } -// @public (undocumented) +// @public @deprecated (undocumented) export const discoveryServiceFactory: () => ServiceFactory< DiscoveryService, 'plugin' @@ -137,7 +137,7 @@ export interface ExtendedHttpServer extends http.Server { stop(): Promise; } -// @public +// @public @deprecated export class HostDiscovery implements DiscoveryService { static fromConfig( config: Config, @@ -190,13 +190,13 @@ export type HttpServerOptions = { }; }; -// @public +// @public @deprecated export type IdentityFactoryOptions = { issuer?: string; algorithms?: string[]; }; -// @public (undocumented) +// @public @deprecated (undocumented) export const identityServiceFactory: ( options?: IdentityFactoryOptions | undefined, ) => ServiceFactory; @@ -208,7 +208,7 @@ export interface LifecycleMiddlewareOptions { startupRequestPauseTimeout?: HumanDuration; } -// @public +// @public @deprecated export const lifecycleServiceFactory: () => ServiceFactory< LifecycleService, 'plugin' @@ -255,7 +255,7 @@ export interface MiddlewareFactoryOptions { logger: LoggerService; } -// @public (undocumented) +// @public @deprecated (undocumented) export const permissionsServiceFactory: () => ServiceFactory< PermissionsService, 'plugin' @@ -270,7 +270,7 @@ export function readHelmetOptions(config?: Config): HelmetOptions; // @public export function readHttpServerOptions(config?: Config): HttpServerOptions; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RootConfigFactoryOptions { argv?: string[]; remote?: Pick; @@ -278,7 +278,7 @@ export interface RootConfigFactoryOptions { watch?: boolean; } -// @public (undocumented) +// @public @deprecated (undocumented) export const rootConfigServiceFactory: ( options?: RootConfigFactoryOptions | undefined, ) => ServiceFactory; @@ -314,7 +314,7 @@ export const rootHttpRouterServiceFactory: ( options?: RootHttpRouterFactoryOptions | undefined, ) => ServiceFactory; -// @public +// @public @deprecated export const rootLifecycleServiceFactory: () => ServiceFactory< RootLifecycleService, 'root' @@ -332,13 +332,13 @@ export const schedulerServiceFactory: () => ServiceFactory< 'plugin' >; -// @public (undocumented) +// @public @deprecated (undocumented) export const tokenManagerServiceFactory: () => ServiceFactory< TokenManagerService, 'plugin' >; -// @public (undocumented) +// @public @deprecated (undocumented) export const urlReaderServiceFactory: () => ServiceFactory; // @public (undocumented) diff --git a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts index b91356a13a..cf2d4c5044 100644 --- a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts @@ -20,7 +20,10 @@ import { createServiceFactory, } from '@backstage/backend-plugin-api'; -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/cache` instead. + */ export const cacheServiceFactory = createServiceFactory({ service: coreServices.cache, deps: { diff --git a/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts b/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts index 68103842bb..c74474e629 100644 --- a/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts @@ -23,7 +23,10 @@ import { RemoteConfigSourceOptions, } from '@backstage/config-loader'; -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. + */ export interface RootConfigFactoryOptions { /** * Process arguments to use instead of the default `process.argv()`. @@ -37,7 +40,10 @@ export interface RootConfigFactoryOptions { watch?: boolean; } -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. + */ export const rootConfigServiceFactory = createServiceFactory( (options?: RootConfigFactoryOptions) => ({ service: coreServices.rootConfig, diff --git a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts b/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts index 139609b6c1..972d8dd4ec 100644 --- a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts @@ -21,7 +21,10 @@ import { } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/database` instead. + */ export const databaseServiceFactory = createServiceFactory({ service: coreServices.database, deps: { diff --git a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts b/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts index 180c8706d5..d337da997a 100644 --- a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts +++ b/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts @@ -29,6 +29,7 @@ type Target = string | { internal: string; external: string }; * resolved to the same host, so there won't be any balancing of internal traffic. * * @public + * @deprecated Please import from `@backstage/backend-defaults/discovery` instead. */ export class HostDiscovery implements DiscoveryService { /** diff --git a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts b/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts index bfc5a6a489..b29a589438 100644 --- a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts @@ -20,7 +20,10 @@ import { } from '@backstage/backend-plugin-api'; import { HostDiscovery } from './HostDiscovery'; -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/discovery` instead. + */ export const discoveryServiceFactory = createServiceFactory({ service: coreServices.discovery, deps: { diff --git a/packages/backend-app-api/src/services/implementations/identity/identityServiceFactory.ts b/packages/backend-app-api/src/services/implementations/identity/identityServiceFactory.ts index 58da1c37d5..c2f1a61007 100644 --- a/packages/backend-app-api/src/services/implementations/identity/identityServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/identity/identityServiceFactory.ts @@ -24,16 +24,22 @@ import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; * An identity client options object which allows extra configurations * * @public + * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead */ export type IdentityFactoryOptions = { issuer?: string; - /** JWS "alg" (Algorithm) Header Parameter values. Defaults to an array containing just ES256. - * More info on supported algorithms: https://github.com/panva/jose */ + /** + * JWS "alg" (Algorithm) Header Parameter values. Defaults to an array containing just ES256. + * More info on supported algorithms: https://github.com/panva/jose + */ algorithms?: string[]; }; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead + */ export const identityServiceFactory = createServiceFactory( (options?: IdentityFactoryOptions) => ({ service: coreServices.identity, diff --git a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts b/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts index 2b68a81807..b3b0135a7c 100644 --- a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts @@ -26,7 +26,10 @@ import { createServiceFactory, } from '@backstage/backend-plugin-api'; -/** @internal */ +/** + * @internal + * @deprecated + */ export class BackendPluginLifecycleImpl implements LifecycleService { constructor( private readonly logger: LoggerService, @@ -85,7 +88,9 @@ export class BackendPluginLifecycleImpl implements LifecycleService { /** * Allows plugins to register shutdown hooks that are run when the process is about to exit. + * * @public + * @deprecated Please import from `@backstage/backend-defaults/lifecycle` instead. */ export const lifecycleServiceFactory = createServiceFactory({ service: coreServices.lifecycle, diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts index 9824eb2145..c8fa0e7bbf 100644 --- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts @@ -20,7 +20,10 @@ import { } from '@backstage/backend-plugin-api'; import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/permissions` instead. + */ export const permissionsServiceFactory = createServiceFactory({ service: coreServices.permissions, deps: { diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts index 197f99b97a..bf5dc09b80 100644 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts @@ -25,7 +25,10 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; -/** @internal */ +/** + * @internal + * @deprecated + */ export class BackendLifecycleImpl implements RootLifecycleService { constructor(private readonly logger: LoggerService) {} @@ -108,6 +111,7 @@ export class BackendLifecycleImpl implements RootLifecycleService { * Allows plugins to register shutdown hooks that are run when the process is about to exit. * * @public + * @deprecated Please import from `@backstage/backend-defaults/rootLifecycle` instead. */ export const rootLifecycleServiceFactory = createServiceFactory({ service: coreServices.rootLifecycle, diff --git a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts index cad1b06035..e7c4ce7af0 100644 --- a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts @@ -20,7 +20,10 @@ import { } from '@backstage/backend-plugin-api'; import { ServerTokenManager } from '@backstage/backend-common'; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead + */ export const tokenManagerServiceFactory = createServiceFactory({ service: coreServices.tokenManager, deps: { diff --git a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts b/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts index 7f404a24b1..44caf25ad6 100644 --- a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts @@ -20,7 +20,10 @@ import { createServiceFactory, } from '@backstage/backend-plugin-api'; -/** @public */ +/** + * @public + * @deprecated Please import from `@backstage/backend-defaults/urlReader` instead. + */ export const urlReaderServiceFactory = createServiceFactory({ service: coreServices.urlReader, deps: { diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 1bf38a8c40..ddad94de14 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -20,9 +20,9 @@ import { coreServices, ServiceRef, ServiceFactory, + LifecycleService, + RootLifecycleService, } from '@backstage/backend-plugin-api'; -import { BackendLifecycleImpl } from '../services/implementations/rootLifecycle/rootLifecycleServiceFactory'; -import { BackendPluginLifecycleImpl } from '../services/implementations/lifecycle/lifecycleServiceFactory'; import { ServiceOrExtensionPoint } from './types'; // Direct internal import to avoid duplication // eslint-disable-next-line @backstage/no-forbidden-package-imports @@ -345,27 +345,42 @@ export class BackendInitializer { } // Bit of a hacky way to grab the lifecycle services, potentially find a nicer way to do this - async #getRootLifecycleImpl(): Promise { + async #getRootLifecycleImpl(): Promise< + RootLifecycleService & { + startup(): Promise; + shutdown(): Promise; + } + > { const lifecycleService = await this.#serviceRegistry.get( coreServices.rootLifecycle, 'root', ); - if (lifecycleService instanceof BackendLifecycleImpl) { - return lifecycleService; + + const service = lifecycleService as any; + if ( + service && + typeof service.startup === 'function' && + typeof service.shutdown === 'function' + ) { + return service; } + throw new Error('Unexpected root lifecycle service implementation'); } async #getPluginLifecycleImpl( pluginId: string, - ): Promise { + ): Promise }> { const lifecycleService = await this.#serviceRegistry.get( coreServices.lifecycle, pluginId, ); - if (lifecycleService instanceof BackendPluginLifecycleImpl) { - return lifecycleService; + + const service = lifecycleService as any; + if (service && typeof service.startup === 'function') { + return service; } + throw new Error('Unexpected plugin lifecycle service implementation'); } } diff --git a/packages/backend-defaults/api-report-cache.md b/packages/backend-defaults/api-report-cache.md new file mode 100644 index 0000000000..150ed391f0 --- /dev/null +++ b/packages/backend-defaults/api-report-cache.md @@ -0,0 +1,13 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { CacheClient } from '@backstage/backend-common'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const cacheServiceFactory: () => ServiceFactory; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/api-report-database.md b/packages/backend-defaults/api-report-database.md new file mode 100644 index 0000000000..512e1febc9 --- /dev/null +++ b/packages/backend-defaults/api-report-database.md @@ -0,0 +1,16 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const databaseServiceFactory: () => ServiceFactory< + PluginDatabaseManager, + 'plugin' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/api-report-discovery.md b/packages/backend-defaults/api-report-discovery.md new file mode 100644 index 0000000000..0ff734b8a7 --- /dev/null +++ b/packages/backend-defaults/api-report-discovery.md @@ -0,0 +1,31 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { Config } from '@backstage/config'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const discoveryServiceFactory: () => ServiceFactory< + DiscoveryService, + 'plugin' +>; + +// @public +export class HostDiscovery implements DiscoveryService { + static fromConfig( + config: Config, + options?: { + basePath?: string; + }, + ): HostDiscovery; + // (undocumented) + getBaseUrl(pluginId: string): Promise; + // (undocumented) + getExternalBaseUrl(pluginId: string): Promise; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/api-report-lifecycle.md b/packages/backend-defaults/api-report-lifecycle.md new file mode 100644 index 0000000000..4233584664 --- /dev/null +++ b/packages/backend-defaults/api-report-lifecycle.md @@ -0,0 +1,16 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { LifecycleService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public +export const lifecycleServiceFactory: () => ServiceFactory< + LifecycleService, + 'plugin' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/api-report-permissions.md b/packages/backend-defaults/api-report-permissions.md new file mode 100644 index 0000000000..9006a62018 --- /dev/null +++ b/packages/backend-defaults/api-report-permissions.md @@ -0,0 +1,16 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { PermissionsService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export const permissionsServiceFactory: () => ServiceFactory< + PermissionsService, + 'plugin' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/api-report-rootConfig.md b/packages/backend-defaults/api-report-rootConfig.md new file mode 100644 index 0000000000..60c8fbcb57 --- /dev/null +++ b/packages/backend-defaults/api-report-rootConfig.md @@ -0,0 +1,24 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { RemoteConfigSourceOptions } from '@backstage/config-loader'; +import { RootConfigService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export interface RootConfigFactoryOptions { + argv?: string[]; + remote?: Pick; + // (undocumented) + watch?: boolean; +} + +// @public (undocumented) +export const rootConfigServiceFactory: ( + options?: RootConfigFactoryOptions | undefined, +) => ServiceFactory; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/api-report-rootLifecycle.md b/packages/backend-defaults/api-report-rootLifecycle.md new file mode 100644 index 0000000000..00eb9c1ebb --- /dev/null +++ b/packages/backend-defaults/api-report-rootLifecycle.md @@ -0,0 +1,16 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { RootLifecycleService } from '@backstage/backend-plugin-api'; +import { ServiceFactory } from '@backstage/backend-plugin-api'; + +// @public +export const rootLifecycleServiceFactory: () => ServiceFactory< + RootLifecycleService, + 'root' +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/api-report-urlReader.md b/packages/backend-defaults/api-report-urlReader.md new file mode 100644 index 0000000000..8c7147bd7c --- /dev/null +++ b/packages/backend-defaults/api-report-urlReader.md @@ -0,0 +1,13 @@ +## API Report File for "@backstage/backend-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { UrlReader } from '@backstage/backend-common'; + +// @public (undocumented) +export const urlReaderServiceFactory: () => ServiceFactory; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts new file mode 100644 index 0000000000..569ab436db --- /dev/null +++ b/packages/backend-defaults/config.d.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** + * Options used by the default discovery service. + */ + discovery?: { + /** + * A list of target baseUrls and the associated plugins. + */ + endpoints: Array<{ + /** + * The target base URL to use for the plugin. + * + * Can be either a string or an object with internal and external keys. + * Targets with `{{pluginId}}` or `{{ pluginId }} in the URL will be replaced with the plugin ID. + */ + target: string | { internal: string; external: string }; + /** + * Array of plugins which use the target base URL. + */ + plugins: string[]; + }>; + }; +} diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 05c7ea511c..4f502b1598 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -20,22 +20,55 @@ "license": "Apache-2.0", "exports": { ".": "./src/index.ts", + "./cache": "./src/entrypoints/cache/index.ts", + "./database": "./src/entrypoints/database/index.ts", + "./discovery": "./src/entrypoints/discovery/index.ts", + "./lifecycle": "./src/entrypoints/lifecycle/index.ts", + "./permissions": "./src/entrypoints/permissions/index.ts", + "./rootConfig": "./src/entrypoints/rootConfig/index.ts", + "./rootLifecycle": "./src/entrypoints/rootLifecycle/index.ts", "./scheduler": "./src/entrypoints/scheduler/index.ts", + "./urlReader": "./src/entrypoints/urlReader/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", "types": "src/index.ts", "typesVersions": { "*": { + "cache": [ + "src/entrypoints/cache/index.ts" + ], + "database": [ + "src/entrypoints/database/index.ts" + ], + "discovery": [ + "src/entrypoints/discovery/index.ts" + ], + "lifecycle": [ + "src/entrypoints/lifecycle/index.ts" + ], + "permissions": [ + "src/entrypoints/permissions/index.ts" + ], + "rootConfig": [ + "src/entrypoints/rootConfig/index.ts" + ], + "rootLifecycle": [ + "src/entrypoints/rootLifecycle/index.ts" + ], "scheduler": [ "src/entrypoints/scheduler/index.ts" ], + "urlReader": [ + "src/entrypoints/urlReader/index.ts" + ], "package.json": [ "package.json" ] } }, "files": [ + "config.d.ts", "dist", "migrations" ], @@ -52,8 +85,11 @@ "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-events-node": "workspace:^", + "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", "@opentelemetry/api": "^1.3.0", "cron": "^3.0.0", @@ -68,5 +104,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" - } + }, + "configSchema": "config.d.ts" } diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 811f980d12..823b3c2833 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -16,27 +16,27 @@ import { Backend, - cacheServiceFactory, - rootConfigServiceFactory, + authServiceFactory, createSpecializedBackend, - databaseServiceFactory, - discoveryServiceFactory, + httpAuthServiceFactory, httpRouterServiceFactory, - rootHttpRouterServiceFactory, - lifecycleServiceFactory, - rootLifecycleServiceFactory, + identityServiceFactory, loggerServiceFactory, - permissionsServiceFactory, + rootHttpRouterServiceFactory, rootLoggerServiceFactory, tokenManagerServiceFactory, - urlReaderServiceFactory, - identityServiceFactory, - authServiceFactory, - httpAuthServiceFactory, userInfoServiceFactory, } from '@backstage/backend-app-api'; -import { eventsServiceFactory } from '@backstage/plugin-events-node'; +import { cacheServiceFactory } from '@backstage/backend-defaults/cache'; +import { databaseServiceFactory } from '@backstage/backend-defaults/database'; +import { discoveryServiceFactory } from '@backstage/backend-defaults/discovery'; +import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle'; +import { permissionsServiceFactory } from '@backstage/backend-defaults/permissions'; +import { rootConfigServiceFactory } from '@backstage/backend-defaults/rootConfig'; +import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle'; import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler'; +import { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader'; +import { eventsServiceFactory } from '@backstage/plugin-events-node'; export const defaultServiceFactories = [ authServiceFactory(), diff --git a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts new file mode 100644 index 0000000000..d348c455d2 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts @@ -0,0 +1,38 @@ +/* + * 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 { CacheManager } from '@backstage/backend-common'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; + +/** + * @public + */ +export const cacheServiceFactory = createServiceFactory({ + service: coreServices.cache, + deps: { + config: coreServices.rootConfig, + plugin: coreServices.pluginMetadata, + }, + async createRootContext({ config }) { + return CacheManager.fromConfig(config); + }, + async factory({ plugin }, manager) { + return manager.forPlugin(plugin.getId()).getClient(); + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/cache/index.ts b/packages/backend-defaults/src/entrypoints/cache/index.ts new file mode 100644 index 0000000000..f96ee77182 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/cache/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { cacheServiceFactory } from './cacheServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts b/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts new file mode 100644 index 0000000000..12e4e569bd --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/database/databaseServiceFactory.ts @@ -0,0 +1,51 @@ +/* + * 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 { DatabaseManager } from '@backstage/backend-common'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { ConfigReader } from '@backstage/config'; + +/** + * @public + */ +export const databaseServiceFactory = createServiceFactory({ + service: coreServices.database, + deps: { + config: coreServices.rootConfig, + lifecycle: coreServices.lifecycle, + pluginMetadata: coreServices.pluginMetadata, + }, + async createRootContext({ config }) { + return config.getOptional('backend.database') + ? DatabaseManager.fromConfig(config) + : DatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { client: 'better-sqlite3', connection: ':memory:' }, + }, + }), + ); + }, + async factory({ pluginMetadata, lifecycle }, databaseManager) { + return databaseManager.forPlugin(pluginMetadata.getId(), { + pluginMetadata, + lifecycle, + }); + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/database/index.ts b/packages/backend-defaults/src/entrypoints/database/index.ts new file mode 100644 index 0000000000..d676c8013e --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/database/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { databaseServiceFactory } from './databaseServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts new file mode 100644 index 0000000000..4e6aff5853 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.test.ts @@ -0,0 +1,257 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { HostDiscovery } from './HostDiscovery'; + +describe('HostDiscovery', () => { + it('is created from config', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://localhost:80/api/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://localhost:40/api/catalog', + ); + }); + + it('strips trailing slashes in config', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40//', + listen: { port: 80, host: 'localhost' }, + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://localhost:80/api/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://localhost:40/api/catalog', + ); + }); + + it('can configure the base path', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + }), + { basePath: '/service' }, + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://localhost:80/service/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://localhost:40/service/catalog', + ); + }); + + it.each([ + [{ listen: ':80' }, 'http://localhost:80'], + [{ listen: ':40', https: true }, 'https://localhost:40'], + [{ listen: '127.0.0.1:80' }, 'http://127.0.0.1:80'], + [{ listen: '127.0.0.1:80', https: true }, 'https://127.0.0.1:80'], + [{ listen: '0.0.0.0:40' }, 'http://127.0.0.1:40'], + [{ listen: { port: 80 } }, 'http://localhost:80'], + [{ listen: { port: 8000 } }, 'http://localhost:8000'], + [{ listen: { port: 80, host: '0.0.0.0' } }, 'http://127.0.0.1:80'], + [{ listen: { port: 80, host: '::' } }, 'http://localhost:80'], + [{ listen: { port: 80, host: '::1' } }, 'http://[::1]:80'], + [{ listen: { port: 90, host: '::2' }, https: true }, 'https://[::2]:90'], + ])('resolves internal baseUrl for %j as %s', async (config, expected) => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + ...config, + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + `${expected}/api/catalog`, + ); + }); + + it('uses plugin specific targets from config if provided', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: { + internal: 'http://catalog-backend-internal:8080/api/catalog', + external: 'http://catalog-backend-external:8080/api/catalog', + }, + plugins: ['catalog'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://catalog-backend-internal:8080/api/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://catalog-backend-external:8080/api/catalog', + ); + }); + + it('uses a single target for internal and external for a plugin', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: 'http://catalog-backend:8080/api/catalog', + plugins: ['catalog'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://catalog-backend:8080/api/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://catalog-backend:8080/api/catalog', + ); + }); + + it('defaults to the backend baseUrl when there is not an endpoint for a plugin', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: 'http://catalog-backend:8080/api/catalog', + plugins: ['catalog'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( + 'http://localhost:80/api/scaffolder', + ); + await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( + 'http://localhost:40/api/scaffolder', + ); + }); + + it('replaces {{pluginId}} or {{ pluginId }} in the target', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: 'http://common-backend:8080/api/{{pluginId}}', + plugins: ['catalog', 'docs'], + }, + { + target: { + internal: 'http://scaffolder-internal:8080/api/{{ pluginId }}', + external: 'http://scaffolder-external:8080/api/{{ pluginId }}', + }, + plugins: ['scaffolder'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://common-backend:8080/api/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://common-backend:8080/api/catalog', + ); + await expect(discovery.getBaseUrl('docs')).resolves.toBe( + 'http://common-backend:8080/api/docs', + ); + await expect(discovery.getExternalBaseUrl('docs')).resolves.toBe( + 'http://common-backend:8080/api/docs', + ); + await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( + 'http://scaffolder-internal:8080/api/scaffolder', + ); + await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( + 'http://scaffolder-external:8080/api/scaffolder', + ); + }); + + it('encodes the pluginId', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: 'http://common-backend:8080/api/{{pluginId}}', + plugins: ['plugin/beta'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('plugin/beta')).resolves.toBe( + 'http://common-backend:8080/api/plugin%2Fbeta', + ); + await expect(discovery.getBaseUrl('plugin/alpha')).resolves.toBe( + 'http://localhost:80/api/plugin%2Falpha', + ); + await expect(discovery.getExternalBaseUrl('plugin/alpha')).resolves.toBe( + 'http://localhost:40/api/plugin%2Falpha', + ); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts new file mode 100644 index 0000000000..180c8706d5 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/discovery/HostDiscovery.ts @@ -0,0 +1,132 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { readHttpServerOptions } from '@backstage/backend-app-api'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; + +type Target = string | { internal: string; external: string }; + +/** + * HostDiscovery is a basic PluginEndpointDiscovery implementation + * that can handle plugins that are hosted in a single or multiple deployments. + * + * The deployment may be scaled horizontally, as long as the external URL + * is the same for all instances. However, internal URLs will always be + * resolved to the same host, so there won't be any balancing of internal traffic. + * + * @public + */ +export class HostDiscovery implements DiscoveryService { + /** + * Creates a new HostDiscovery discovery instance by reading + * from the `backend` config section, specifically the `.baseUrl` for + * discovering the external URL, and the `.listen` and `.https` config + * for the internal one. + * + * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`. + * eg. + * ```yaml + * discovery: + * endpoints: + * - target: https://internal.example.com/internal-catalog + * plugins: [catalog] + * - target: https://internal.example.com/secure/api/{{pluginId}} + * plugins: [auth, permission] + * - target: + * internal: https://internal.example.com/search + * external: https://example.com/search + * plugins: [search] + * ``` + * + * The basePath defaults to `/api`, meaning the default full internal + * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`. + */ + static fromConfig(config: Config, options?: { basePath?: string }) { + const basePath = options?.basePath ?? '/api'; + const externalBaseUrl = config + .getString('backend.baseUrl') + .replace(/\/+$/, ''); + + const { + listen: { host: listenHost = '::', port: listenPort }, + } = readHttpServerOptions(config.getConfig('backend')); + const protocol = config.has('backend.https') ? 'https' : 'http'; + + // Translate bind-all to localhost, and support IPv6 + let host = listenHost; + if (host === '::' || host === '') { + // We use localhost instead of ::1, since IPv6-compatible systems should default + // to using IPv6 when they see localhost, but if the system doesn't support IPv6 + // things will still work. + host = 'localhost'; + } else if (host === '0.0.0.0') { + host = '127.0.0.1'; + } + if (host.includes(':')) { + host = `[${host}]`; + } + + const internalBaseUrl = `${protocol}://${host}:${listenPort}`; + + return new HostDiscovery( + internalBaseUrl + basePath, + externalBaseUrl + basePath, + config.getOptionalConfig('discovery'), + ); + } + + private constructor( + private readonly internalBaseUrl: string, + private readonly externalBaseUrl: string, + private readonly discoveryConfig: Config | undefined, + ) {} + + private getTargetFromConfig(pluginId: string, type: 'internal' | 'external') { + const endpoints = this.discoveryConfig?.getOptionalConfigArray('endpoints'); + + const target = endpoints + ?.find(endpoint => endpoint.getStringArray('plugins').includes(pluginId)) + ?.get('target'); + + if (!target) { + const baseUrl = + type === 'external' ? this.externalBaseUrl : this.internalBaseUrl; + + return `${baseUrl}/${encodeURIComponent(pluginId)}`; + } + + if (typeof target === 'string') { + return target.replace( + /\{\{\s*pluginId\s*\}\}/g, + encodeURIComponent(pluginId), + ); + } + + return target[type].replace( + /\{\{\s*pluginId\s*\}\}/g, + encodeURIComponent(pluginId), + ); + } + + async getBaseUrl(pluginId: string): Promise { + return this.getTargetFromConfig(pluginId, 'internal'); + } + + async getExternalBaseUrl(pluginId: string): Promise { + return this.getTargetFromConfig(pluginId, 'external'); + } +} diff --git a/packages/backend-defaults/src/entrypoints/discovery/discoveryServiceFactory.ts b/packages/backend-defaults/src/entrypoints/discovery/discoveryServiceFactory.ts new file mode 100644 index 0000000000..bfc5a6a489 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/discovery/discoveryServiceFactory.ts @@ -0,0 +1,32 @@ +/* + * 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 { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { HostDiscovery } from './HostDiscovery'; + +/** @public */ +export const discoveryServiceFactory = createServiceFactory({ + service: coreServices.discovery, + deps: { + config: coreServices.rootConfig, + }, + async factory({ config }) { + return HostDiscovery.fromConfig(config); + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/discovery/index.ts b/packages/backend-defaults/src/entrypoints/discovery/index.ts new file mode 100644 index 0000000000..ee4851271a --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/discovery/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { discoveryServiceFactory } from './discoveryServiceFactory'; +export { HostDiscovery } from './HostDiscovery'; diff --git a/packages/backend-defaults/src/entrypoints/lifecycle/index.ts b/packages/backend-defaults/src/entrypoints/lifecycle/index.ts new file mode 100644 index 0000000000..8dac4c26b4 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/lifecycle/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { lifecycleServiceFactory } from './lifecycleServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/lifecycle/lifecycleServiceFactory.ts b/packages/backend-defaults/src/entrypoints/lifecycle/lifecycleServiceFactory.ts new file mode 100644 index 0000000000..3eb43d6c6e --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/lifecycle/lifecycleServiceFactory.ts @@ -0,0 +1,106 @@ +/* + * 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 { + LifecycleService, + LifecycleServiceShutdownHook, + LifecycleServiceShutdownOptions, + LifecycleServiceStartupHook, + LifecycleServiceStartupOptions, + LoggerService, + PluginMetadataService, + RootLifecycleService, + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; + +/** @internal */ +export class BackendPluginLifecycleImpl implements LifecycleService { + constructor( + private readonly logger: LoggerService, + private readonly rootLifecycle: RootLifecycleService, + private readonly pluginMetadata: PluginMetadataService, + ) {} + + #hasStarted = false; + #startupTasks: Array<{ + hook: LifecycleServiceStartupHook; + options?: LifecycleServiceStartupOptions; + }> = []; + + addStartupHook( + hook: LifecycleServiceStartupHook, + options?: LifecycleServiceStartupOptions, + ): void { + if (this.#hasStarted) { + throw new Error('Attempted to add startup hook after startup'); + } + this.#startupTasks.push({ hook, options }); + } + + async startup(): Promise { + if (this.#hasStarted) { + return; + } + this.#hasStarted = true; + + this.logger.debug( + `Running ${this.#startupTasks.length} plugin startup tasks...`, + ); + await Promise.all( + this.#startupTasks.map(async ({ hook, options }) => { + const logger = options?.logger ?? this.logger; + try { + await hook(); + logger.debug(`Plugin startup hook succeeded`); + } catch (error) { + logger.error(`Plugin startup hook failed, ${error}`); + } + }), + ); + } + + addShutdownHook( + hook: LifecycleServiceShutdownHook, + options?: LifecycleServiceShutdownOptions, + ): void { + const plugin = this.pluginMetadata.getId(); + this.rootLifecycle.addShutdownHook(hook, { + logger: options?.logger?.child({ plugin }) ?? this.logger, + }); + } +} + +/** + * Allows plugins to register shutdown hooks that are run when the process is about to exit. + * + * @public + */ +export const lifecycleServiceFactory = createServiceFactory({ + service: coreServices.lifecycle, + deps: { + logger: coreServices.logger, + rootLifecycle: coreServices.rootLifecycle, + pluginMetadata: coreServices.pluginMetadata, + }, + async factory({ rootLifecycle, logger, pluginMetadata }) { + return new BackendPluginLifecycleImpl( + logger, + rootLifecycle, + pluginMetadata, + ); + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/permissions/index.ts b/packages/backend-defaults/src/entrypoints/permissions/index.ts new file mode 100644 index 0000000000..781dda31a0 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/permissions/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { permissionsServiceFactory } from './permissionsServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/permissions/permissionsServiceFactory.ts b/packages/backend-defaults/src/entrypoints/permissions/permissionsServiceFactory.ts new file mode 100644 index 0000000000..f675dd6719 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/permissions/permissionsServiceFactory.ts @@ -0,0 +1,41 @@ +/* + * 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 { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; + +/** + * @public + */ +export const permissionsServiceFactory = createServiceFactory({ + service: coreServices.permissions, + deps: { + auth: coreServices.auth, + config: coreServices.rootConfig, + discovery: coreServices.discovery, + tokenManager: coreServices.tokenManager, + }, + async factory({ auth, config, discovery, tokenManager }) { + return ServerPermissionClient.fromConfig(config, { + auth, + discovery, + tokenManager, + }); + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/rootConfig/index.ts b/packages/backend-defaults/src/entrypoints/rootConfig/index.ts new file mode 100644 index 0000000000..1775ef2efc --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootConfig/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { rootConfigServiceFactory } from './rootConfigServiceFactory'; +export type { RootConfigFactoryOptions } from './rootConfigServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts new file mode 100644 index 0000000000..92d1a89c0f --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts @@ -0,0 +1,59 @@ +/* + * 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 { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { + ConfigSources, + RemoteConfigSourceOptions, +} from '@backstage/config-loader'; + +/** + * @public + */ +export interface RootConfigFactoryOptions { + /** + * Process arguments to use instead of the default `process.argv()`. + */ + argv?: string[]; + + /** + * Enables and sets options for remote configuration loading. + */ + remote?: Pick; + watch?: boolean; +} + +/** + * @public + */ +export const rootConfigServiceFactory = createServiceFactory( + (options?: RootConfigFactoryOptions) => ({ + service: coreServices.rootConfig, + deps: {}, + async factory() { + const source = ConfigSources.default({ + argv: options?.argv, + remote: options?.remote, + watch: options?.watch, + }); + console.log(`Loading config from ${source}`); + return await ConfigSources.toConfig(source); + }, + }), +); diff --git a/packages/backend-defaults/src/entrypoints/rootLifecycle/index.ts b/packages/backend-defaults/src/entrypoints/rootLifecycle/index.ts new file mode 100644 index 0000000000..86589cd23e --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootLifecycle/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { rootLifecycleServiceFactory } from './rootLifecycleServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.test.ts new file mode 100644 index 0000000000..91cb9031ae --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.test.ts @@ -0,0 +1,60 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { BackendLifecycleImpl } from './rootLifecycleServiceFactory'; + +describe('lifecycleService', () => { + it('should execute registered shutdown hook', async () => { + const service = new BackendLifecycleImpl(getVoidLogger()); + const hook = jest.fn(); + service.addShutdownHook(() => hook()); + // should not execute the hook more than once. + await service.shutdown(); + await service.shutdown(); + await service.shutdown(); + expect(hook).toHaveBeenCalledTimes(1); + }); + + it('should not throw errors', async () => { + const service = new BackendLifecycleImpl(getVoidLogger()); + service.addShutdownHook(() => { + throw new Error('oh no'); + }); + await expect(service.shutdown()).resolves.toBeUndefined(); + }); + + it('should not throw async errors', async () => { + const service = new BackendLifecycleImpl(getVoidLogger()); + service.addShutdownHook(async () => { + throw new Error('oh no'); + }); + await expect(service.shutdown()).resolves.toBeUndefined(); + }); + + it('should reject hooks after trigger', async () => { + const service = new BackendLifecycleImpl(getVoidLogger()); + await service.startup(); + expect(() => { + service.addStartupHook(() => {}); + }).toThrow('Attempted to add startup hook after startup'); + + await service.shutdown(); + expect(() => { + service.addShutdownHook(() => {}); + }).toThrow('Attempted to add shutdown hook after shutdown'); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.ts new file mode 100644 index 0000000000..197f99b97a --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.ts @@ -0,0 +1,120 @@ +/* + * 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 { + createServiceFactory, + coreServices, + LifecycleServiceStartupHook, + LifecycleServiceStartupOptions, + LifecycleServiceShutdownHook, + LifecycleServiceShutdownOptions, + RootLifecycleService, + LoggerService, +} from '@backstage/backend-plugin-api'; + +/** @internal */ +export class BackendLifecycleImpl implements RootLifecycleService { + constructor(private readonly logger: LoggerService) {} + + #hasStarted = false; + #startupTasks: Array<{ + hook: LifecycleServiceStartupHook; + options?: LifecycleServiceStartupOptions; + }> = []; + + addStartupHook( + hook: LifecycleServiceStartupHook, + options?: LifecycleServiceStartupOptions, + ): void { + if (this.#hasStarted) { + throw new Error('Attempted to add startup hook after startup'); + } + this.#startupTasks.push({ hook, options }); + } + + async startup(): Promise { + if (this.#hasStarted) { + return; + } + this.#hasStarted = true; + + this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`); + await Promise.all( + this.#startupTasks.map(async ({ hook, options }) => { + const logger = options?.logger ?? this.logger; + try { + await hook(); + logger.debug(`Startup hook succeeded`); + } catch (error) { + logger.error(`Startup hook failed, ${error}`); + } + }), + ); + } + + #hasShutdown = false; + #shutdownTasks: Array<{ + hook: LifecycleServiceShutdownHook; + options?: LifecycleServiceShutdownOptions; + }> = []; + + addShutdownHook( + hook: LifecycleServiceShutdownHook, + options?: LifecycleServiceShutdownOptions, + ): void { + if (this.#hasShutdown) { + throw new Error('Attempted to add shutdown hook after shutdown'); + } + this.#shutdownTasks.push({ hook, options }); + } + + async shutdown(): Promise { + if (this.#hasShutdown) { + return; + } + this.#hasShutdown = true; + + this.logger.debug( + `Running ${this.#shutdownTasks.length} shutdown tasks...`, + ); + await Promise.all( + this.#shutdownTasks.map(async ({ hook, options }) => { + const logger = options?.logger ?? this.logger; + try { + await hook(); + logger.debug(`Shutdown hook succeeded`); + } catch (error) { + logger.error(`Shutdown hook failed, ${error}`); + } + }), + ); + } +} + +/** + * Allows plugins to register shutdown hooks that are run when the process is about to exit. + * + * @public + */ +export const rootLifecycleServiceFactory = createServiceFactory({ + service: coreServices.rootLifecycle, + deps: { + logger: coreServices.rootLogger, + }, + async factory({ logger }) { + return new BackendLifecycleImpl(logger); + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/index.ts b/packages/backend-defaults/src/entrypoints/urlReader/index.ts new file mode 100644 index 0000000000..6a4b9f65be --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/urlReader/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { urlReaderServiceFactory } from './urlReaderServiceFactory'; diff --git a/packages/backend-defaults/src/entrypoints/urlReader/urlReaderServiceFactory.ts b/packages/backend-defaults/src/entrypoints/urlReader/urlReaderServiceFactory.ts new file mode 100644 index 0000000000..7f404a24b1 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/urlReader/urlReaderServiceFactory.ts @@ -0,0 +1,36 @@ +/* + * 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 { UrlReaders } from '@backstage/backend-common'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; + +/** @public */ +export const urlReaderServiceFactory = createServiceFactory({ + service: coreServices.urlReader, + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + }, + async factory({ config, logger }) { + return UrlReaders.default({ + config, + logger, + }); + }, +}); diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 1ba2de768a..85ae233dfe 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -204,9 +204,11 @@ export namespace coreServices { const rootLifecycle: ServiceRef; const rootLogger: ServiceRef; const scheduler: ServiceRef; - const tokenManager: ServiceRef; + const // @deprecated + tokenManager: ServiceRef; const urlReader: ServiceRef; - const identity: ServiceRef; + const // @deprecated + identity: ServiceRef; } // @public diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index c760afc7b4..4e02611f80 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -172,6 +172,7 @@ export namespace coreServices { * The service reference for the plugin scoped {@link TokenManagerService}. * * @public + * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead */ export const tokenManager = createServiceRef< import('./TokenManagerService').TokenManagerService @@ -190,6 +191,7 @@ export namespace coreServices { * The service reference for the plugin scoped {@link IdentityService}. * * @public + * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead */ export const identity = createServiceRef< import('./IdentityService').IdentityService diff --git a/yarn.lock b/yarn.lock index c143baf388..f0dc705a0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3433,8 +3433,11 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-events-node": "workspace:^" + "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" "@opentelemetry/api": ^1.3.0 cron: ^3.0.0 From a98f851133504ea8a3baead913b51382f052b74b Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 15 May 2024 14:04:31 +0200 Subject: [PATCH 448/567] refactor: create a void logger service impl Signed-off-by: Camila Belo --- packages/backend-app-api/api-report.md | 16 +++++++ .../backend-app-api/src/logging/VoidLogger.ts | 44 +++++++++++++++++++ packages/backend-app-api/src/logging/index.ts | 1 + .../src/next/services/mockServices.ts | 2 +- plugins/search-backend-module-pg/package.json | 1 + .../PgSearchEngine/PgSearchEngineIndexer.ts | 5 ++- yarn.lock | 1 + 7 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 packages/backend-app-api/src/logging/VoidLogger.ts diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 5720c1c60f..1d681c308b 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -347,6 +347,22 @@ export const userInfoServiceFactory: () => ServiceFactory< 'plugin' >; +// @public +export class VoidLogger implements RootLoggerService { + // (undocumented) + child(_meta: JsonObject): LoggerService; + // (undocumented) + static create(): VoidLogger; + // (undocumented) + debug(_message: string, _meta?: JsonObject): void; + // (undocumented) + error(_message: string, _meta?: JsonObject): void; + // (undocumented) + info(_message: string, _meta?: JsonObject): void; + // (undocumented) + warn(_message: string, _meta?: JsonObject): void; +} + // @public export class WinstonLogger implements RootLoggerService { // (undocumented) diff --git a/packages/backend-app-api/src/logging/VoidLogger.ts b/packages/backend-app-api/src/logging/VoidLogger.ts new file mode 100644 index 0000000000..0f5acba07f --- /dev/null +++ b/packages/backend-app-api/src/logging/VoidLogger.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + LoggerService, + RootLoggerService, +} from '@backstage/backend-plugin-api'; +import { JsonObject } from '@backstage/types'; + +/** + * An empty {@link @backstage/backend-plugin-api#LoggerService} implementation. + * + * @public + */ +export class VoidLogger implements RootLoggerService { + static create(): VoidLogger { + return new VoidLogger(); + } + + error(_message: string, _meta?: JsonObject): void {} + + warn(_message: string, _meta?: JsonObject): void {} + + info(_message: string, _meta?: JsonObject): void {} + + debug(_message: string, _meta?: JsonObject): void {} + + child(_meta: JsonObject): LoggerService { + return new VoidLogger(); + } +} diff --git a/packages/backend-app-api/src/logging/index.ts b/packages/backend-app-api/src/logging/index.ts index 14fe33f898..a7162553e3 100644 --- a/packages/backend-app-api/src/logging/index.ts +++ b/packages/backend-app-api/src/logging/index.ts @@ -14,5 +14,6 @@ * limitations under the License. */ +export { VoidLogger } from './VoidLogger'; export { WinstonLogger } from './WinstonLogger'; export type { WinstonLoggerOptions } from './WinstonLogger'; diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index a486a9ab0c..57621a9209 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -60,7 +60,7 @@ import { /** @internal */ function createLoggerMock() { return { - child: jest.fn().mockImplementation(() => createLoggerMock()), + child: jest.fn().mockImplementation(createLoggerMock), debug: jest.fn(), error: jest.fn(), info: jest.fn(), diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 6645e2642f..6d61b7a050 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -47,6 +47,7 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts index 9fe0e7091c..93b699fc74 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { Knex } from 'knex'; import { Logger } from 'winston'; import { DatabaseStore } from '../database'; +import { VoidLogger } from '@backstage/backend-app-api'; /** @public */ export type PgSearchEngineIndexerOptions = { @@ -41,7 +42,7 @@ export class PgSearchEngineIndexer extends BatchSearchEngineIndexer { super({ batchSize: options.batchSize }); this.store = options.databaseStore; this.type = options.type; - this.logger = options.logger || getVoidLogger(); + this.logger = options.logger || loggerToWinstonLogger(VoidLogger.create()); } async initialize(): Promise { diff --git a/yarn.lock b/yarn.lock index 8ff4221b13..10c2ff9895 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6933,6 +6933,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-search-backend-module-pg@workspace:plugins/search-backend-module-pg" dependencies: + "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" From 6a576dc0d5fd94e918b3930780d5d15c2ee886fd Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 16 May 2024 09:57:38 +0200 Subject: [PATCH 449/567] docs: add changeset files Signed-off-by: Camila Belo --- .changeset/late-ants-impress.md | 11 +++++++++++ .changeset/old-trees-check.md | 5 +++++ .changeset/olive-mangos-tickle.md | 5 +++++ .changeset/sour-colts-juggle.md | 5 +++++ .changeset/wise-vans-sin.md | 5 +++++ 5 files changed, 31 insertions(+) create mode 100644 .changeset/late-ants-impress.md create mode 100644 .changeset/old-trees-check.md create mode 100644 .changeset/olive-mangos-tickle.md create mode 100644 .changeset/sour-colts-juggle.md create mode 100644 .changeset/wise-vans-sin.md diff --git a/.changeset/late-ants-impress.md b/.changeset/late-ants-impress.md new file mode 100644 index 0000000000..c694cea7c4 --- /dev/null +++ b/.changeset/late-ants-impress.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-node-test-utils': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-signals-backend': patch +'@backstage/plugin-events-node': patch +--- + +Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-deprecate `backend-common` package. diff --git a/.changeset/old-trees-check.md b/.changeset/old-trees-check.md new file mode 100644 index 0000000000..4c259bf4d7 --- /dev/null +++ b/.changeset/old-trees-check.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Deprecate the legacy `TaskScheduler.fromConfig` method and stop using the `getVoidlogger` in tests files to reduce the dependecy on the soon-to-deprecate `backstage-common` package. diff --git a/.changeset/olive-mangos-tickle.md b/.changeset/olive-mangos-tickle.md new file mode 100644 index 0000000000..d28d250ca3 --- /dev/null +++ b/.changeset/olive-mangos-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Export a new `VoidLogger` implementation and stop using `getVoidLogger` in tests to reduce the dependecy on the soon-to-deprecate `backstage-common` package. diff --git a/.changeset/sour-colts-juggle.md b/.changeset/sour-colts-juggle.md new file mode 100644 index 0000000000..35dc79452a --- /dev/null +++ b/.changeset/sour-colts-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Fix the logger service mock to prevent returning `undefined` from the `child` method. diff --git a/.changeset/wise-vans-sin.md b/.changeset/wise-vans-sin.md new file mode 100644 index 0000000000..37565c9300 --- /dev/null +++ b/.changeset/wise-vans-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Deprecate legacy service logger helpers and stop using `getVoidLogger` in tests. From 702fa7d17cc34c8d5c414529e21f7c1b775d9316 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 May 2024 11:32:58 +0200 Subject: [PATCH 450/567] theme: fix v5 prefix tree shaking Signed-off-by: Patrik Oldsberg --- .changeset/lovely-hats-pay.md | 5 ++++ .../theme/src/unified/MuiClassNameSetup.ts | 24 ------------------- .../src/unified/UnifiedThemeProvider.test.tsx | 1 - .../src/unified/UnifiedThemeProvider.tsx | 15 +++++++++--- 4 files changed, 17 insertions(+), 28 deletions(-) create mode 100644 .changeset/lovely-hats-pay.md delete mode 100644 packages/theme/src/unified/MuiClassNameSetup.ts diff --git a/.changeset/lovely-hats-pay.md b/.changeset/lovely-hats-pay.md new file mode 100644 index 0000000000..117f50e5db --- /dev/null +++ b/.changeset/lovely-hats-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/theme': patch +--- + +Internal refactor to fix an issue where the MUI 5 `v5-` class prefixing gets removed by tree shaking. diff --git a/packages/theme/src/unified/MuiClassNameSetup.ts b/packages/theme/src/unified/MuiClassNameSetup.ts deleted file mode 100644 index 5d7e0b26fe..0000000000 --- a/packages/theme/src/unified/MuiClassNameSetup.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className'; - -/** - * This API is introduced in @mui/material (v5.0.5) as a replacement of deprecated createGenerateClassName & only affects v5 Material UI components from `@mui/*` - */ -ClassNameGenerator.configure(componentName => { - return `v5-${componentName}`; -}); diff --git a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx index da4af5abab..98fc73505d 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.test.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.test.tsx @@ -22,7 +22,6 @@ import { useTheme as useV5Theme } from '@mui/material/styles'; import { makeStyles as makeV5Styles } from '@mui/styles'; import { render, screen } from '@testing-library/react'; import React from 'react'; -import './MuiClassNameSetup'; import { UnifiedThemeProvider } from './UnifiedThemeProvider'; import { themes } from './themes'; diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index 35abbe57d8..465a4dda24 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -15,7 +15,6 @@ */ import React, { ReactNode } from 'react'; -import './MuiClassNameSetup'; import { CssBaseline } from '@material-ui/core'; import { ThemeProvider, @@ -29,6 +28,7 @@ import { Theme as Mui5Theme, } from '@mui/material/styles'; import { UnifiedTheme } from './types'; +import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className'; /** * Props for {@link UnifiedThemeProvider}. @@ -41,10 +41,19 @@ export interface UnifiedThemeProviderProps { noCssBaseline?: boolean; } +/** + * This API is introduced in @mui/material (v5.0.5) as a replacement of deprecated createGenerateClassName & only affects v5 Material UI components from `@mui/*`. + * + * This call needs to be in the same module as the `UnifiedThemeProvider` to ensure that it doesn't get removed by tree shaking + */ +ClassNameGenerator.configure(componentName => { + return `v5-${componentName}`; +}); + // Background at https://mui.com/x/migration/migration-data-grid-v4/#using-mui-core-v4-with-v5 // Rather than disabling globals and custom seed, we instead only set a production prefix that -// won't collide with Material UI 5 styles. We've already got a separate class name generator for v5 set -// up in MuiClassNameSetup.ts, so only the production JSS needs deduplication. +// won't collide with Material UI 5 styles. We've already got the separate class name generator +// for v5 set up in just above, so only the production JSS needs deduplication. const generateV4ClassName = createGenerateClassName({ productionPrefix: 'jss4-', }); From 71123dc3ef0220fa0986d7f899fc1a95a9a74875 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 16 May 2024 13:04:28 +0200 Subject: [PATCH 451/567] Incorporated the feedback Signed-off-by: bnechyporenko --- beps/0001-notifications-system/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/beps/0001-notifications-system/README.md b/beps/0001-notifications-system/README.md index 86b299387c..80751179dd 100644 --- a/beps/0001-notifications-system/README.md +++ b/beps/0001-notifications-system/README.md @@ -92,7 +92,7 @@ If the notification status is updated, the signal service shall emit a signal wi The role of the notifications plugin is to manage the lifecycle of notifications. The backend plugin provides an API for other backends to send notifications, as well as an accompanying [backend service](https://backstage.io/docs/backend-system/architecture/services). It also provides a separate API for the frontend plugin to read notifications for an individual user and manage the read status of notifications. -The notification backend stores notification using the [database service](https://backstage.io/docs/backend-system/core-services/index#database). In particular it needs to store the following information for each notification: +The notification backend stores notification using the [database service](https://backstage.io/docs/backend-system/core-services/index#database). In particular, it needs to store the following information for each notification: - ID - Recipients @@ -112,7 +112,7 @@ The notification backend stores notification using the [database service](https: - Icon (optional) - Metadata (optional) -The recipients is **not** a list of users, but rather a filter that describes who should receive the notification. It must be possible to evaluate this filter in a database query, so that we can efficiently fetch all notifications for a given user. The same filter will also be used by the signal backend to determine which users should receive a signal. +The recipients are **not** a list of users, but rather a filter that describes who should receive the notification. It must be possible to evaluate this filter in a database query, so that we can efficiently fetch all notifications for a given user. The same filter will also be used by the signal backend to determine which users should receive a signal. The read date is a timestamp of marking the notifications as read by the user. If missing, the notification is still unread. @@ -209,12 +209,16 @@ export type NotificationSeverity = 'critical' | 'high' | 'normal' | 'low'; export type NotificationPayload = { title: string; description?: string; - link: string; + link?: string; additionalLinks?: string[]; - severity: NotificationSeverity; + severity?: NotificationSeverity; topic?: string; scope?: string; icon?: string; + metadata?: Array<{ + type: string; + value: JsonValue; + }>; }; export type Notification = { @@ -239,7 +243,8 @@ interface NotificationService { } ``` -Each notification contains a human readable `title`, `origin` and optionally `link` for additional details. The `created`, `id`, `read` and `saved` properties are handled by the backend based and cannot be passed during the notification creation. +Each notification contains a human-readable `title`, `origin` and optionally `link` for additional details. The `created`, `id`, `read` and `saved` properties are handled by the backend based and cannot be passed during the notification creation. +Any optional additional details could be stored in `metadata`. We advise to provide the name to the type which contains the information about the context and the version, for example: 'core.icon.v1'. Calling `sendNotification` should never throw an error so that it doesn't block the current processing. Notifications should be considered as second-level citizens that are not critical if not delivered. From 8fb30a5afe10405f7c7c005ad56ff2f31373f80d Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 16 May 2024 14:42:25 +0200 Subject: [PATCH 452/567] refator: deprecate create service builder Signed-off-by: Camila Belo --- packages/backend-common/api-report.md | 2 +- packages/backend-common/src/service/createServiceBuilder.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 42df32dc1d..0f33539ed2 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -265,7 +265,7 @@ export function createRootLogger( env?: NodeJS.ProcessEnv, ): winston.Logger; -// @public +// @public @deprecated export function createServiceBuilder(_module: NodeModule): ServiceBuilder; // @public diff --git a/packages/backend-common/src/service/createServiceBuilder.ts b/packages/backend-common/src/service/createServiceBuilder.ts index a6ca25415e..5a4f439a32 100644 --- a/packages/backend-common/src/service/createServiceBuilder.ts +++ b/packages/backend-common/src/service/createServiceBuilder.ts @@ -19,8 +19,8 @@ import { ServiceBuilder } from './types'; /** * Creates a new service builder. - * * @public + * @deprecated We are going to deprecated this old way of creating services in a near future, if you are using this service helper, please checkout the migration guide and make sure you migrate your backend to use the new system: https://backstage.io/docs/backend-system/building-backends/migrating. */ export function createServiceBuilder(_module: NodeModule): ServiceBuilder { return new ServiceBuilderImpl(_module); From b1ea88559cf79ef2d4061305e47dd52f7f6f67bf Mon Sep 17 00:00:00 2001 From: Brian Phillips <28457+brianphillips@users.noreply.github.com> Date: Thu, 16 May 2024 08:11:09 -0500 Subject: [PATCH 453/567] rename method per code review feedback Signed-off-by: Brian Phillips <28457+brianphillips@users.noreply.github.com> --- plugins/search-backend-node/api-report-alpha.md | 4 ++-- plugins/search-backend-node/src/alpha.ts | 8 ++++---- plugins/search-backend/src/alpha.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/search-backend-node/api-report-alpha.md b/plugins/search-backend-node/api-report-alpha.md index 4c1e70b32b..4f3015a0e4 100644 --- a/plugins/search-backend-node/api-report-alpha.md +++ b/plugins/search-backend-node/api-report-alpha.md @@ -32,14 +32,14 @@ export const searchIndexRegistryExtensionPoint: ExtensionPoint; + init(options: SearchIndexServiceInitOptions): void; start(): Promise; stop(): Promise; } // @alpha -export type SearchIndexServiceBuildOptions = { +export type SearchIndexServiceInitOptions = { searchEngine: SearchEngine; collators: RegisterCollatorParameters[]; decorators: RegisterDecoratorParameters[]; diff --git a/plugins/search-backend-node/src/alpha.ts b/plugins/search-backend-node/src/alpha.ts index 20b0f22089..2a41bdbf81 100644 --- a/plugins/search-backend-node/src/alpha.ts +++ b/plugins/search-backend-node/src/alpha.ts @@ -32,9 +32,9 @@ import { /** * @alpha - * Options for build method on {@link SearchIndexService}. + * Options for the init method on {@link SearchIndexService}. */ -export type SearchIndexServiceBuildOptions = { +export type SearchIndexServiceInitOptions = { searchEngine: SearchEngine; collators: RegisterCollatorParameters[]; decorators: RegisterDecoratorParameters[]; @@ -48,7 +48,7 @@ export interface SearchIndexService { /** * Initializes state in preparation for starting the search index service */ - build(options: SearchIndexServiceBuildOptions): void; + init(options: SearchIndexServiceInitOptions): void; /** * Starts indexing process @@ -104,7 +104,7 @@ class DefaultSearchIndexService implements SearchIndexService { return new DefaultSearchIndexService(options); } - build(options: SearchIndexServiceBuildOptions): void { + init(options: SearchIndexServiceInitOptions): void { this.indexBuilder = new IndexBuilder({ logger: this.logger, searchEngine: options.searchEngine, diff --git a/plugins/search-backend/src/alpha.ts b/plugins/search-backend/src/alpha.ts index a7c2c6c872..3a3ba82c3e 100644 --- a/plugins/search-backend/src/alpha.ts +++ b/plugins/search-backend/src/alpha.ts @@ -121,7 +121,7 @@ export default createBackendPlugin({ const collators = searchIndexRegistry.getCollators(); const decorators = searchIndexRegistry.getDecorators(); - searchIndexService.build({ + searchIndexService.init({ searchEngine: searchEngine!, collators, decorators, From 8721a02da15268e02cbfe7e11d71ab7fa228a5e4 Mon Sep 17 00:00:00 2001 From: Symbat Nurbay Date: Thu, 16 May 2024 15:28:33 +0200 Subject: [PATCH 454/567] repo-tools: add additional properties to generate command Signed-off-by: Symbat Nurbay --- .changeset/strong-moose-work.md | 5 +++++ packages/repo-tools/src/commands/index.ts | 4 ++++ .../package/schema/openapi/generate/client.ts | 16 +++++++++++++--- .../package/schema/openapi/generate/index.ts | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 .changeset/strong-moose-work.md diff --git a/.changeset/strong-moose-work.md b/.changeset/strong-moose-work.md new file mode 100644 index 0000000000..0c9cf01d94 --- /dev/null +++ b/.changeset/strong-moose-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': minor +--- + +Add --additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 83266ea9f9..a07b01077f 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -54,6 +54,10 @@ function registerPackageCommand(program: Command) { .description( 'Command to generate a client and/or a server stub from an OpenAPI spec.', ) + .option('--additional-properties [properties]') + .description( + 'Additional properties that can be passed to @openapitools/openapi-generator-cli', + ) .action( lazy(() => import('./package/schema/openapi/generate').then(m => m.command), diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 6963e02664..1f4b2e5152 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -27,12 +27,18 @@ import { exec } from '../../../../../lib/exec'; import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers'; -async function generate(outputDirectory: string) { +async function generate( + outputDirectory: string, + additionalProperties?: string, +) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); const resolvedOutputDirectory = cliPaths.resolveTargetRoot( outputDirectory, OUTPUT_PATH, ); + const openapiProperties = additionalProperties + ? `--additional-properties=${additionalProperties}` + : ''; mkdirpSync(resolvedOutputDirectory); await fs.mkdirp(resolvedOutputDirectory); @@ -60,6 +66,7 @@ async function generate(outputDirectory: string) { ), '--generator-key', 'v3.0', + openapiProperties, ], { maxBuffer: Number.MAX_VALUE, @@ -87,9 +94,12 @@ async function generate(outputDirectory: string) { }); } -export async function command(outputPackage: string): Promise { +export async function command( + outputPackage: string, + additionalProperties?: string, +): Promise { try { - await generate(outputPackage); + await generate(outputPackage, additionalProperties); console.log( chalk.green(`Generated client in ${outputPackage}/${OUTPUT_PATH}`), ); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts index 1e48fe3825..5356db3244 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -26,7 +26,7 @@ export async function command(opts: OptionValues) { process.exit(1); } if (opts.clientPackage) { - await generateClient(opts.clientPackage); + await generateClient(opts.clientPackage, opts.additionalProperties); } if (opts.server) { await generateServer(); From 42189602af0b49a3cce3e68a4c5d159eefe9b42e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 May 2024 15:33:12 +0200 Subject: [PATCH 455/567] Apply suggestions from code review Co-authored-by: Emma Indal Signed-off-by: Patrik Oldsberg --- .changeset/seven-geese-raise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/seven-geese-raise.md b/.changeset/seven-geese-raise.md index 176f6ae479..3d6e2347b9 100644 --- a/.changeset/seven-geese-raise.md +++ b/.changeset/seven-geese-raise.md @@ -3,4 +3,4 @@ '@backstage/plugin-search-backend': patch --- -Split backend search plugin startup into "build" and "start" stages to ensure necessary initialization has happened before startup +Split backend search plugin startup into "init" and "start" stages to ensure necessary initialization has happened before startup From 021d4cbfd1cc5deaddef68f6dcbe808deea5fbf0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 16 May 2024 15:42:09 +0200 Subject: [PATCH 456/567] Update plugins/search-backend-node/src/alpha.ts Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: Patrik Oldsberg --- plugins/search-backend-node/src/alpha.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-backend-node/src/alpha.ts b/plugins/search-backend-node/src/alpha.ts index 2a41bdbf81..6fb0c609e5 100644 --- a/plugins/search-backend-node/src/alpha.ts +++ b/plugins/search-backend-node/src/alpha.ts @@ -121,7 +121,7 @@ class DefaultSearchIndexService implements SearchIndexService { async start(): Promise { if (!this.indexBuilder) { - throw new Error('IndexBuilder is not initialized, call build first'); + throw new Error('IndexBuilder is not initialized, call init first'); } const { scheduler } = await this.indexBuilder.build(); this.scheduler = scheduler; From 654af4ab0b29c7ab770834f2335fd9ea6e49448b Mon Sep 17 00:00:00 2001 From: Kalle Ericson <7943407+kalleericson@users.noreply.github.com> Date: Thu, 16 May 2024 15:49:55 +0200 Subject: [PATCH 457/567] fix: add missing css variables Signed-off-by: Kalle Ericson <7943407+kalleericson@users.noreply.github.com> --- .changeset/calm-plums-wink.md | 5 +++++ .../src/reader/transformers/styles/rules/variables.ts | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 .changeset/calm-plums-wink.md diff --git a/.changeset/calm-plums-wink.md b/.changeset/calm-plums-wink.md new file mode 100644 index 0000000000..edb991f616 --- /dev/null +++ b/.changeset/calm-plums-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +mkdocs-material have updated their CSS variable template, and a few are unset in Backstage. This patch adds the missing variables to ensure coverage. diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts index a858d17354..3999430efb 100644 --- a/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts +++ b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts @@ -58,6 +58,9 @@ export default ({ theme }: RuleOptions) => ` /* ACCENT */ --md-accent-fg-color: var(--md-primary-fg-color); + --md-accent-fg-color--transparent: ${alpha(theme.palette.primary.main, 0.1)}; + --md-accent-bg-color: var(--md-primary-bg-color); + --md-accent-bg-color--light: var(--md-primary-bg-color--light); /* SHADOW */ --md-shadow-z1: ${theme.shadows[1]}; @@ -101,6 +104,7 @@ export default ({ theme }: RuleOptions) => ` --md-code-fg-color: ${theme.palette.text.primary}; --md-code-bg-color: ${theme.palette.background.paper}; --md-code-hl-color: ${alpha(theme.palette.warning.main, 0.5)}; + --md-code-hl-color--light: var(--md-code-hl-color); --md-code-hl-keyword-color: ${ theme.palette.type === 'dark' ? theme.palette.primary.light @@ -135,6 +139,7 @@ export default ({ theme }: RuleOptions) => ` --md-typeset-color: var(--md-default-fg-color); --md-typeset-a-color: ${theme.palette.link}; --md-typeset-table-color: ${theme.palette.text.primary}; + --md-typeset-table-color--light: ${alpha(theme.palette.text.primary, 0.5)}; --md-typeset-del-color: ${ theme.palette.type === 'dark' ? alpha(theme.palette.error.dark, 0.5) @@ -150,6 +155,9 @@ export default ({ theme }: RuleOptions) => ` ? alpha(theme.palette.warning.dark, 0.5) : alpha(theme.palette.warning.light, 0.5) }; + --md-typeset-kbd-color: var(--md-code-bg-color); + --md-typeset-kbd-accent-color var(--md-code-bg-color); + --md-typeset-kbd-border-color: var(--md-default-fg-color--light); } @media screen and (max-width: 76.1875em) { @@ -165,4 +173,7 @@ export default ({ theme }: RuleOptions) => ` --md-typeset-font-size: .7rem; } } + + --md-footer-bg-color: var(--md-default-bg-color); + --md-footer-bg-color--dark: var(--md-default-bg-color); `; From c2be13dd114fde69b28af123f37d1823663bae79 Mon Sep 17 00:00:00 2001 From: Mihai Tabara Date: Thu, 16 May 2024 22:08:26 +0100 Subject: [PATCH 458/567] Sort entries in table Signed-off-by: Mihai Tabara --- OWNERS.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OWNERS.md b/OWNERS.md index ec593b956f..bf97c91685 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -24,13 +24,13 @@ Scope: The catalog plugin and catalog model | Name | Organization | Team | GitHub | Discord | | -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | -| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | -| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | | Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | -| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | -| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | | Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | | Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| 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 @@ -76,13 +76,13 @@ Scope: The Permission Framework and plugins integrating with the permission fram | Name | Organization | Team | GitHub | Discord | | -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | -| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | -| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | | Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | -| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | -| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | | Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Johan Haals | Spotify | Cubic Belugas | [jhaals](https://github.com/jhaals) | `Johan#0679` | | Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | ### TechDocs From d1e0b2d0d4ac4467c58671ca0354b671aed935c2 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Fri, 17 May 2024 08:40:28 +0530 Subject: [PATCH 459/567] Updated the features documents Signed-off-by: Aditya Kumar --- docs/features/kubernetes/installation.md | 6 +++- docs/features/search/search-engines.md | 12 ++++--- .../software-catalog/catalog-customization.md | 18 ++++++++-- .../software-catalog/life-of-an-entity.md | 10 ++++-- .../software-templates/adding-templates.md | 17 +++++---- ...uthorizing-parameters-steps-and-actions.md | 6 +++- .../software-templates/builtin-actions.md | 6 +++- .../software-templates/configuration.md | 6 +++- docs/features/software-templates/index.md | 12 ++++--- .../migrating-to-rjsf-v5.md | 6 +++- .../writing-custom-actions.md | 12 ++++--- docs/features/techdocs/architecture.md | 11 ++++-- docs/features/techdocs/getting-started.md | 6 +++- docs/features/techdocs/how-to-guides.md | 36 +++++++++++++------ docs/features/techdocs/using-cloud-storage.md | 26 ++++++++------ 15 files changed, 136 insertions(+), 54 deletions(-) diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md index 42ceaaa016..b3d03cc0b4 100644 --- a/docs/features/kubernetes/installation.md +++ b/docs/features/kubernetes/installation.md @@ -236,7 +236,11 @@ backend.add(kubernetesModuleCustomClusterDiscovery); backend.start(); ``` -> Note: this example assumes the `CustomClustersSupplier` class is the same from the [previous example](#custom-cluster-discovery) +:::note Note + +This example assumes the `CustomClustersSupplier` class is the same from the [previous example](#custom-cluster-discovery) + +::: ## Configuration diff --git a/docs/features/search/search-engines.md b/docs/features/search/search-engines.md index d06e401847..86b6244fb6 100644 --- a/docs/features/search/search-engines.md +++ b/docs/features/search/search-engines.md @@ -34,10 +34,14 @@ const searchEngine = new LunrSearchEngine({ logger: env.logger }); const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); ``` -> Note: Lunr is appropriate as a zero-config search engine when developing -> other parts of Backstage locally, however its use is highly discouraged when -> running Backstage in production. When deploying Backstage, use one of the -> other search engines instead. +:::note Note + +Lunr is appropriate as a zero-config search engine when developing +other parts of Backstage locally, however its use is highly discouraged when +running Backstage in production. When deploying Backstage, use one of the +other search engines instead. + +::: ## Postgres diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 3dc47a610a..0c8eb7bcda 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -95,7 +95,11 @@ const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => { } /> ``` -> Note: the above example has been simplified and you will most likely have more code then just this in your `App.tsx` file. +:::note Note + +The above example has been simplified and you will most likely have more code then just this in your `App.tsx` file. + +::: ## Customize Actions @@ -162,7 +166,11 @@ const customActions: TableProps['actions'] = [ } /> ``` -> Note: the above example has been simplified and you will most likely have more code then just this in your `App.tsx` file. +:::note Note + +The above example has been simplified and you will most likely have more code then just this in your `App.tsx` file. + +::: The above customization will override the existing actions. Currently the only way to keep them and add your own is to also include the existing actions in your array by copying them from the [`defaultActions`](https://github.com/backstage/backstage/blob/57397e7d6d2d725712c439f4ab93f2ac6aa27bf8/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx#L113-L168). @@ -400,7 +408,11 @@ export const CustomCatalogPage = () => { The above is a very basic version of a fully custom `CatalogIndexPage`, you'll want to explore the various props to see what you can all do with them. This was built off the building blocks seen in the [`DefaultCatalogPage`](https://github.com/backstage/backstage/blob/master/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx) -> Note: The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically. +:::note Note + +The catalog index page is designed to have a minimal code footprint to support easy customization, but creating a replica does introduce a possibility of drifting out of date over time. Be sure to check the catalog [CHANGELOG](https://github.com/backstage/backstage/blob/master/plugins/catalog/CHANGELOG.md) periodically. + +::: To use this custom `CatalogIndexPage` which we called `CustomCatalogPage`, you'll need to make the following change: diff --git a/docs/features/software-catalog/life-of-an-entity.md b/docs/features/software-catalog/life-of-an-entity.md index d1c01141e4..13fe496fc1 100644 --- a/docs/features/software-catalog/life-of-an-entity.md +++ b/docs/features/software-catalog/life-of-an-entity.md @@ -162,9 +162,13 @@ steps and merging them into the final object which is what is visible from the catalog API. As the final entity itself gets updated, the stitcher makes sure that the search table gets refreshed accordingly as well. -> Note: The search table mentioned here is not related to the core Search -> feature of Backstage. It's rather the table that backs the ability to filter -> catalog API query results. +:::note Note + +The search table mentioned here is not related to the core Search +feature of Backstage. It's rather the table that backs the ability to filter +catalog API query results. + +::: ![Stitching overview](../../assets/features/catalog/life-of-an-entity_stitching.svg) diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index 6396d7530a..083516192c 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -86,12 +86,17 @@ contains more information about the required fields. Once we have a `template.yaml` ready, we can then add it to the software catalog for use by the scaffolder. -> Note: When you add or modify a template, you will need to refresh the location entity. -> Otherwise, Backstage won't display the template in the available templates, -> or it will keep showing the old template. You can refresh the location instance by -> going into `Catalog` web page, choosing `Locations` instead of `Components`, and selecting the correct location entity. -> From there, you can click on the refresh icon representing "Scheduled entity refresh" action. -> Afterwards, you should see your template updated. +:::note Note + +When you add or modify a template, you will need to refresh the location entity. +Otherwise, Backstage won't display the template in the available templates, +or it will keep showing the old template. You can refresh the location instance by +going into `Catalog` web page, choosing `Locations` instead of `Components`, and selecting the correct +location entity. +From there, you can click on the refresh icon representing "Scheduled entity refresh" action. +Afterwards, you should see your template updated. + +::: You can add the template files to the catalog through [static location configuration](../software-catalog/configuration.md#static-location-configuration), diff --git a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-parameters-steps-and-actions.md index 072f750a14..105c336538 100644 --- a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-parameters-steps-and-actions.md @@ -229,4 +229,8 @@ backend.add(customPermissionBackendModule); /* highlight-add-end */ ``` -> Note: the `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them. +:::note Note + +The `ExamplePermissionPolicy` here could be the one from the [Authorizing parameters and steps](#authorizing-parameters-and-steps) example or from the [Authorizing actions](#authorizing-actions) example. It would work the same way for both of them. + +::: diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index 94b2d2298b..5b1e4347da 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -57,7 +57,11 @@ backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); backend.start(); ``` -> Note: This is a simplified example of what your backend may look like, you may have more code in here then this. +:::note Note + +This is a simplified example of what your backend may look like, you may have more code in here then this. + +::: ## Listing Actions diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index 24b8c530b8..571f1150d0 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -12,7 +12,11 @@ This is done in your `app-config.yaml` by adding [Backstage integrations](https://backstage.io/docs/integrations/) for the appropriate source code repository for your organization. -> Note: Integrations may already be set up as part of your `app-config.yaml`. +:::note Note + +Integrations may already be set up as part of your `app-config.yaml`. + +::: The next step is to [add templates](http://backstage.io/docs/features/software-templates/adding-templates) to your Backstage app. diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index 9642214ede..d4fa6ac971 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -20,10 +20,14 @@ locations like GitHub or GitLab. > Be sure to have covered > [Getting Started with Backstage](../../getting-started) before proceeding. -> Note: if you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to -> use the templates feature. -> One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage: -> `export NODE_OPTIONS=--no-node-snapshot` +:::note Note + +If you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to +use the templates feature. +One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage: +`export NODE_OPTIONS=--no-node-snapshot` + +::: The Software Templates are available under `/create`. For local development you should be able to reach them at `http://localhost:3000/create`. diff --git a/docs/features/software-templates/migrating-to-rjsf-v5.md b/docs/features/software-templates/migrating-to-rjsf-v5.md index 829aaf8410..856841bdd4 100644 --- a/docs/features/software-templates/migrating-to-rjsf-v5.md +++ b/docs/features/software-templates/migrating-to-rjsf-v5.md @@ -5,7 +5,11 @@ title: 'Migrating to react-jsonschema-form@v5' description: Docs on migrating to `react-jsonschema-form`@v5 and the new designs --- -> Note: If you were previously using the `/alpha` imports to test out the `scaffolder/next` work, those imports have been promoted to the default exports from the respective packages. You should just have to remove the `/alpha` from the import path, and remove the `Next` from the import name. `NextScaffolderPage` -> `ScaffolderPage`, `createNextScaffolderFieldExtension` -> `createScaffolderFieldExtension` etc. +:::note Note + +If you were previously using the `/alpha` imports to test out the `scaffolder/next` work, those imports have been promoted to the default exports from the respective packages. You should just have to remove the `/alpha` from the import path, and remove the `Next` from the import name. `NextScaffolderPage` -> `ScaffolderPage`, `createNextScaffolderFieldExtension` -> `createScaffolderFieldExtension` etc. + +::: ## What's `react-jsonschema-form`? diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index 92ff092948..3b358e4c14 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -8,10 +8,14 @@ If you want to extend the functionality of the Scaffolder, you can do so by writing custom actions which can be used alongside our [built-in actions](./builtin-actions.md). -> Note: When adding custom actions, the actions array will **replace the -> built-in actions too**. Meaning, you will no longer be able to use them. -> If you want to continue using the builtin actions, include them in the actions -> array when registering your custom actions, as seen below. +:::note Note + +When adding custom actions, the actions array will **replace the +built-in actions too**. Meaning, you will no longer be able to use them. +If you want to continue using the builtin actions, include them in the actions +array when registering your custom actions, as seen below. + +::: ## Writing your Custom Action diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md index 2cb8543ccd..8c9c4401cc 100644 --- a/docs/features/techdocs/architecture.md +++ b/docs/features/techdocs/architecture.md @@ -13,9 +13,14 @@ out-of-the box experience. ![TechDocs Architecture diagram](../../assets/techdocs/architecture-basic.drawio.svg) -> Note: See below for our recommended deployment architecture which takes care -> of stability, scalability and speed. Also look at the -> [HOW TO migrate guide](how-to-guides.md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach). +:::note Note + +See below for our recommended deployment architecture which takes care +of stability, scalability and speed. Also look at the +[HOW TO migrate guide](how-to-guides +md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach). + +::: When you open a TechDocs site in Backstage, the [TechDocs Reader](./concepts.md#techdocs-reader) makes a request to diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index bf55c90af9..6cbbc83614 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -219,7 +219,11 @@ backend.add(import('@backstage/plugin-techdocs-backend/alpha')); backend.start(); ``` -> Note: The above is a very simplified example, you may have more content then this in your version. +:::note Note + +The above is a very simplified example, you may have more content then this in your version. + +::: ## Setting the configuration diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index b25c2a3928..3a7cfd97bb 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -499,8 +499,12 @@ Start writing your documentation by adding more markdown (.md) files to this folder (/docs) or replace the content in this file. ``` -> Note: The values of `site_name`, `component_id` and `site_description` depends -> on how you have configured your `template.yaml` +:::note Note + +The values of `site_name`, `component_id` and `site_description` depends +on how you have configured your `template.yaml`. + +::: Done! You now have support for TechDocs in your own software template! @@ -514,7 +518,11 @@ theme: font: false ``` -> Note: The addition `name: material` is necessary. Otherwise it will not work +:::note Note + +The addition `name: material` is necessary. Otherwise it will not work + +::: ## How to enable iframes in TechDocs @@ -623,12 +631,16 @@ plugins: - kroki ``` -> Note: you will very likely want to set a `kroki` `ServerURL` configuration in your -> `mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If -> you have sensitive information in your organization's diagrams, you should set -> up a [server of your own](https://docs.kroki.io/kroki/setup/install/) and use it -> instead. Check out [mkdocs-kroki-plugin config](https://github.com/AVATEAM-IT-SYSTEMHAUS/mkdocs-kroki-plugin#config) -> for more plugin configuration details. +:::note Note + +You will very likely want to set a `kroki` `ServerURL` configuration in your +`mkdocs.yml` as well. The default value is the publicly hosted `kroki.io`. If +you have sensitive information in your organization's diagrams, you should set +up a [server of your own](https://docs.kroki.io/kroki/setup/install/) and use it +instead. Check out [mkdocs-kroki-plugin config](https://github.com/AVATEAM-IT-SYSTEMHAUS/mkdocs-kroki-plugin#config) +for more plugin configuration details. + +::: 4. **Add mermaid code into TechDocs:** @@ -766,7 +778,11 @@ backend.add(techdocsCustomBuildStrategy()); backend.start(); ``` -> Note: You may need to add the `@backstage/plugin-techdocs-node` package to your backend `package.json` if it's not been imported already. +:::note Note + +You may need to add the `@backstage/plugin-techdocs-node` package to your backend `package.json` if it's not been imported already. + +::: ## How to use other mkdocs plugins? diff --git a/docs/features/techdocs/using-cloud-storage.md b/docs/features/techdocs/using-cloud-storage.md index aaf2758241..6ba826ea62 100644 --- a/docs/features/techdocs/using-cloud-storage.md +++ b/docs/features/techdocs/using-cloud-storage.md @@ -169,17 +169,21 @@ permissions to: - `s3:ListBucket` - To retrieve bucket metadata - `s3:GetObject` - To retrieve files from the bucket -> Note: If you need to migrate documentation objects from an older-style path -> format including case-sensitive entity metadata, you will need to add some -> additional permissions to be able to perform the migration, including: -> -> - `s3:PutBucketAcl` (for copying files, -> [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html)) -> - `s3:DeleteObject` and `s3:DeleteObjectVersion` (for deleting migrated files, -> [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html)) -> -> ...And you will need to ensure the permissions apply to the bucket itself, as -> well as all resources under the bucket. See the example policy below. +:::note Note + +If you need to migrate documentation objects from an older-style path +format including case-sensitive entity metadata, you will need to add some +additional permissions to be able to perform the migration, including: + +- `s3:PutBucketAcl` (for copying files, + [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html)) +- `s3:DeleteObject` and `s3:DeleteObjectVersion` (for deleting migrated files, + [more info here](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html)) + +...And you will need to ensure the permissions apply to the bucket itself, as +well as all resources under the bucket. See the example policy below. + +::: ```json { From 0714031ca8b1bd8342dc192b73b5443e514bcf06 Mon Sep 17 00:00:00 2001 From: Symbat Nurbay Date: Fri, 17 May 2024 10:15:23 +0200 Subject: [PATCH 460/567] repo-tools: rename --additional-properties to --client-additional-properties Signed-off-by: Symbat Nurbay --- packages/repo-tools/src/commands/index.ts | 2 +- .../package/schema/openapi/generate/client.ts | 12 ++++++------ .../package/schema/openapi/generate/index.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index a07b01077f..24ec2af411 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -54,7 +54,7 @@ function registerPackageCommand(program: Command) { .description( 'Command to generate a client and/or a server stub from an OpenAPI spec.', ) - .option('--additional-properties [properties]') + .option('--client-additional-properties [properties]') .description( 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts index 1f4b2e5152..b843cd27ec 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts @@ -29,15 +29,15 @@ import { getPathToCurrentOpenApiSpec } from '../../../../../lib/openapi/helpers' async function generate( outputDirectory: string, - additionalProperties?: string, + clientAdditionalProperties?: string, ) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); const resolvedOutputDirectory = cliPaths.resolveTargetRoot( outputDirectory, OUTPUT_PATH, ); - const openapiProperties = additionalProperties - ? `--additional-properties=${additionalProperties}` + const additionalProperties = clientAdditionalProperties + ? `--additional-properties=${clientAdditionalProperties}` : ''; mkdirpSync(resolvedOutputDirectory); @@ -66,7 +66,7 @@ async function generate( ), '--generator-key', 'v3.0', - openapiProperties, + additionalProperties, ], { maxBuffer: Number.MAX_VALUE, @@ -96,10 +96,10 @@ async function generate( export async function command( outputPackage: string, - additionalProperties?: string, + clientAdditionalProperties?: string, ): Promise { try { - await generate(outputPackage, additionalProperties); + await generate(outputPackage, clientAdditionalProperties); console.log( chalk.green(`Generated client in ${outputPackage}/${OUTPUT_PATH}`), ); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts index 5356db3244..41884e2e90 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -26,7 +26,7 @@ export async function command(opts: OptionValues) { process.exit(1); } if (opts.clientPackage) { - await generateClient(opts.clientPackage, opts.additionalProperties); + await generateClient(opts.clientPackage, opts.clientAdditionalProperties); } if (opts.server) { await generateServer(); From 70b51b218b97900d2fa74a1e512cc651269d6fc7 Mon Sep 17 00:00:00 2001 From: Symbat Nurbay Date: Fri, 17 May 2024 10:20:14 +0200 Subject: [PATCH 461/567] repo-tools: change changeset Signed-off-by: Symbat Nurbay --- .changeset/strong-moose-work.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/strong-moose-work.md b/.changeset/strong-moose-work.md index 0c9cf01d94..20d35da7f0 100644 --- a/.changeset/strong-moose-work.md +++ b/.changeset/strong-moose-work.md @@ -2,4 +2,4 @@ '@backstage/repo-tools': minor --- -Add --additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli +Add --client-additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli From 8869b8ef301b6b41c00f79d06ae2c031f5b52b37 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 16 May 2024 14:46:37 +0200 Subject: [PATCH 462/567] refactor: stop using the legacy standalone server Signed-off-by: Camila Belo --- .changeset/cyan-snails-peel.md | 12 ++ .changeset/many-moles-sing.md | 5 + plugins/app-backend/README.md | 14 ++- .../src/run.ts => app-backend/dev/index.ts} | 21 +--- plugins/app-backend/package.json | 1 + .../src/service/standaloneServer.ts | 51 --------- .../src/service/standaloneServer.ts | 66 ----------- plugins/catalog-backend/dev/index.ts | 21 ++++ plugins/catalog-backend/package.json | 1 + plugins/catalog-backend/src/run.ts | 33 ------ .../src/service/standaloneServer.ts | 86 -------------- plugins/devtools-backend/dev/index.ts | 21 ++++ plugins/devtools-backend/package.json | 1 + plugins/devtools-backend/src/run.ts | 33 ------ .../src/service/standaloneServer.ts | 69 ------------ .../example-todo-list-backend/dev/index.ts | 21 ++++ .../example-todo-list-backend/package.json | 1 + plugins/example-todo-list-backend/src/run.ts | 33 ------ .../src/service/standaloneServer.ts | 61 ---------- plugins/proxy-backend/dev/index.ts | 21 ++++ plugins/proxy-backend/package.json | 1 + plugins/proxy-backend/src/run.ts | 33 ------ .../src/service/standaloneServer.ts | 61 ---------- .../{src/run.ts => dev/index.ts} | 20 +--- plugins/search-backend/package.json | 1 + .../src/service/standaloneServer.ts | 79 ------------- plugins/techdocs-backend/dev/index.ts | 21 ++++ plugins/techdocs-backend/package.json | 1 + .../src/service/standaloneServer.ts | 105 ------------------ plugins/user-settings-backend/dev/index.ts | 49 ++++++++ plugins/user-settings-backend/package.json | 1 + plugins/user-settings-backend/src/run.ts | 34 ------ .../src/service/standaloneServer.ts | 83 -------------- yarn.lock | 8 ++ 34 files changed, 209 insertions(+), 860 deletions(-) create mode 100644 .changeset/cyan-snails-peel.md create mode 100644 .changeset/many-moles-sing.md rename plugins/{auth-backend/src/run.ts => app-backend/dev/index.ts} (57%) delete mode 100644 plugins/app-backend/src/service/standaloneServer.ts delete mode 100644 plugins/auth-backend/src/service/standaloneServer.ts create mode 100644 plugins/catalog-backend/dev/index.ts delete mode 100644 plugins/catalog-backend/src/run.ts delete mode 100644 plugins/catalog-backend/src/service/standaloneServer.ts create mode 100644 plugins/devtools-backend/dev/index.ts delete mode 100644 plugins/devtools-backend/src/run.ts delete mode 100644 plugins/devtools-backend/src/service/standaloneServer.ts create mode 100644 plugins/example-todo-list-backend/dev/index.ts delete mode 100644 plugins/example-todo-list-backend/src/run.ts delete mode 100644 plugins/example-todo-list-backend/src/service/standaloneServer.ts create mode 100644 plugins/proxy-backend/dev/index.ts delete mode 100644 plugins/proxy-backend/src/run.ts delete mode 100644 plugins/proxy-backend/src/service/standaloneServer.ts rename plugins/search-backend/{src/run.ts => dev/index.ts} (53%) delete mode 100644 plugins/search-backend/src/service/standaloneServer.ts create mode 100644 plugins/techdocs-backend/dev/index.ts delete mode 100644 plugins/techdocs-backend/src/service/standaloneServer.ts create mode 100644 plugins/user-settings-backend/dev/index.ts delete mode 100644 plugins/user-settings-backend/src/run.ts delete mode 100644 plugins/user-settings-backend/src/service/standaloneServer.ts diff --git a/.changeset/cyan-snails-peel.md b/.changeset/cyan-snails-peel.md new file mode 100644 index 0000000000..4bb159b70f --- /dev/null +++ b/.changeset/cyan-snails-peel.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-user-settings-backend': patch +'@backstage/plugin-devtools-backend': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-proxy-backend': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-app-backend': patch +--- + +Migrate the `dev` server to use the new backend system. diff --git a/.changeset/many-moles-sing.md b/.changeset/many-moles-sing.md new file mode 100644 index 0000000000..e1f5203ed6 --- /dev/null +++ b/.changeset/many-moles-sing.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Deprecate the legacy `createServiceBuilder`. diff --git a/plugins/app-backend/README.md b/plugins/app-backend/README.md index 0f0c235ab5..4e95b90566 100644 --- a/plugins/app-backend/README.md +++ b/plugins/app-backend/README.md @@ -13,7 +13,19 @@ yarn --cwd packages/backend add @backstage/plugin-app-backend app By adding the app package as a dependency we ensure that it is built as part of the backend, and that it can be resolved at runtime. -Now add the plugin router to your app, creating it for example like this: +Now add the plugin to your app, creating it for example like this: + +### New Backend + +```ts +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('@backstage/plugin-app-backend/alpha')); +backend.start(); +``` + +### Old Backend ```ts const router = await createRouter({ diff --git a/plugins/auth-backend/src/run.ts b/plugins/app-backend/dev/index.ts similarity index 57% rename from plugins/auth-backend/src/run.ts rename to plugins/app-backend/dev/index.ts index 7732bbd41a..43c75d24df 100644 --- a/plugins/auth-backend/src/run.ts +++ b/plugins/app-backend/dev/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,19 +14,8 @@ * limitations under the License. */ -import { getRootLogger } from '@backstage/backend-common'; -import { startStandaloneServer } from './service/standaloneServer'; +import { createBackend } from '@backstage/backend-defaults'; -const logger = getRootLogger(); - -startStandaloneServer({ logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); - -module.hot?.accept(); +const backend = createBackend(); +backend.add(import('../src/alpha')); +backend.start(); diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index d0b07f4041..4d53cdb407 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -66,6 +66,7 @@ }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/app-backend/src/service/standaloneServer.ts b/plugins/app-backend/src/service/standaloneServer.ts deleted file mode 100644 index f218c9942e..0000000000 --- a/plugins/app-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Server } from 'http'; -import { createServiceBuilder } from '@backstage/backend-common'; -import { Config } from '@backstage/config'; -import { createRouter } from './router'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - config: Config; - logger: LoggerService; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'app-backend' }); - logger.debug('Starting application server...'); - const router = await createRouter({ - logger, - config: options.config, - appPackageName: 'example-app', - }); - - const service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('', router); - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/auth-backend/src/service/standaloneServer.ts b/plugins/auth-backend/src/service/standaloneServer.ts deleted file mode 100644 index 834305553b..0000000000 --- a/plugins/auth-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceBuilder, - loadBackendConfig, - ServerTokenManager, - HostDiscovery, - DatabaseManager, -} from '@backstage/backend-common'; -import { Server } from 'http'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { createRouter } from './router'; -import { ConfigReader } from '@backstage/config'; - -export interface ServerOptions { - logger: LoggerService; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'auth-backend' }); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = HostDiscovery.fromConfig(config); - - const manager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { client: 'better-sqlite3', connection: ':memory:' }, - }, - }), - ); - const database = manager.forPlugin('auth'); - - logger.debug('Starting application server...'); - const router = await createRouter({ - logger, - config, - database, - discovery, - tokenManager: ServerTokenManager.noop(), - }); - - const service = createServiceBuilder(module) - .enableCors({ origin: 'http://localhost:3000', credentials: true }) - .addRouter('/auth', router); - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} diff --git a/plugins/catalog-backend/dev/index.ts b/plugins/catalog-backend/dev/index.ts new file mode 100644 index 0000000000..43c75d24df --- /dev/null +++ b/plugins/catalog-backend/dev/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('../src/alpha')); +backend.start(); diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 44f35dccf2..bdfcecf2b1 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -91,6 +91,7 @@ "zod": "^3.22.4" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", diff --git a/plugins/catalog-backend/src/run.ts b/plugins/catalog-backend/src/run.ts deleted file mode 100644 index 0a3ed2b7f0..0000000000 --- a/plugins/catalog-backend/src/run.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts deleted file mode 100644 index 0755ab6301..0000000000 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceBuilder, - DatabaseManager, - HostDiscovery, - loadBackendConfig, - ServerTokenManager, - UrlReaders, -} from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { Server } from 'http'; -import { applyDatabaseMigrations } from '../database/migrations'; -import { CatalogBuilder } from './CatalogBuilder'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: LoggerService; -} - -// TODO(freben): Migrate to the next catalog when it's in place -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'catalog-backend' }); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const reader = UrlReaders.default({ logger, config }); - const manager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { client: 'better-sqlite3', connection: ':memory:' }, - }, - }), - ); - const database = manager.forPlugin('catalog'); - const discovery = HostDiscovery.fromConfig(config); - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - - logger.debug('Creating application...'); - await applyDatabaseMigrations(await database.getClient()); - const builder = CatalogBuilder.create({ - logger, - database, - config, - reader, - permissions, - }); - const catalog = await builder.build(); - - logger.debug('Starting application server...'); - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/catalog', catalog.router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/devtools-backend/dev/index.ts b/plugins/devtools-backend/dev/index.ts new file mode 100644 index 0000000000..dc287d46cc --- /dev/null +++ b/plugins/devtools-backend/dev/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('../src')); +backend.start(); diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 00d8df80ae..35695d05cf 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -52,6 +52,7 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/ping": "^0.4.1", diff --git a/plugins/devtools-backend/src/run.ts b/plugins/devtools-backend/src/run.ts deleted file mode 100644 index d945aa13f0..0000000000 --- a/plugins/devtools-backend/src/run.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/devtools-backend/src/service/standaloneServer.ts b/plugins/devtools-backend/src/service/standaloneServer.ts deleted file mode 100644 index 136db7bf89..0000000000 --- a/plugins/devtools-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceBuilder, - HostDiscovery, - loadBackendConfig, - ServerTokenManager, -} from '@backstage/backend-common'; - -import { Server } from 'http'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { createRouter } from './router'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: LoggerService; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'devtools-backend-backend' }); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = HostDiscovery.fromConfig(config); - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - logger.debug('Starting application server...'); - const router = await createRouter({ - logger, - config, - permissions, - discovery, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/devtools-backend', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/example-todo-list-backend/dev/index.ts b/plugins/example-todo-list-backend/dev/index.ts new file mode 100644 index 0000000000..dc287d46cc --- /dev/null +++ b/plugins/example-todo-list-backend/dev/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('../src')); +backend.start(); diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 15a9d16ee9..e8cd617be0 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -43,6 +43,7 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", diff --git a/plugins/example-todo-list-backend/src/run.ts b/plugins/example-todo-list-backend/src/run.ts deleted file mode 100644 index 0a3ed2b7f0..0000000000 --- a/plugins/example-todo-list-backend/src/run.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/example-todo-list-backend/src/service/standaloneServer.ts b/plugins/example-todo-list-backend/src/service/standaloneServer.ts deleted file mode 100644 index 7ed0c064b7..0000000000 --- a/plugins/example-todo-list-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceBuilder, - HostDiscovery, - loadBackendConfig, -} from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { Server } from 'http'; -import { createRouter } from './router'; -import { LoggerService } from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: LoggerService; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'todo-list-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = HostDiscovery.fromConfig(config); - const router = await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/todo-list', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/proxy-backend/dev/index.ts b/plugins/proxy-backend/dev/index.ts new file mode 100644 index 0000000000..43c75d24df --- /dev/null +++ b/plugins/proxy-backend/dev/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('../src/alpha')); +backend.start(); diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 0614956a18..039ceb3898 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -64,6 +64,7 @@ "yup": "^1.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", diff --git a/plugins/proxy-backend/src/run.ts b/plugins/proxy-backend/src/run.ts deleted file mode 100644 index 0a3ed2b7f0..0000000000 --- a/plugins/proxy-backend/src/run.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/proxy-backend/src/service/standaloneServer.ts b/plugins/proxy-backend/src/service/standaloneServer.ts deleted file mode 100644 index 44a1e34090..0000000000 --- a/plugins/proxy-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceBuilder, - loadBackendConfig, - HostDiscovery, -} from '@backstage/backend-common'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'proxy-backend' }); - - logger.debug('Creating application...'); - - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = HostDiscovery.fromConfig(config); - const router = await createRouter({ - config, - logger, - discovery, - }); - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/proxy', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - logger.debug('Starting application server...'); - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/search-backend/src/run.ts b/plugins/search-backend/dev/index.ts similarity index 53% rename from plugins/search-backend/src/run.ts rename to plugins/search-backend/dev/index.ts index 53d4e4334a..9c108c0de1 100644 --- a/plugins/search-backend/src/run.ts +++ b/plugins/search-backend/dev/index.ts @@ -14,20 +14,8 @@ * limitations under the License. */ -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; +import { createBackend } from '@backstage/backend-defaults'; -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); +const backend = createBackend(); +backend.add(import('../src/alpha')); +backend.start(); diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 146e50d177..06a6b5a3ff 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -49,6 +49,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/search-backend/src/service/standaloneServer.ts b/plugins/search-backend/src/service/standaloneServer.ts deleted file mode 100644 index 09fa4f87ee..0000000000 --- a/plugins/search-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceBuilder, - loadBackendConfig, - ServerTokenManager, - HostDiscovery, -} from '@backstage/backend-common'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; -import { - LunrSearchEngine, - IndexBuilder, -} from '@backstage/plugin-search-backend-node'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'search-backend' }); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const searchEngine = new LunrSearchEngine({ logger }); - const indexBuilder = new IndexBuilder({ logger, searchEngine }); - const discovery = HostDiscovery.fromConfig(config); - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - logger.debug('Starting application server...'); - - // TODO: stub out some documents/indices? - - const router = await createRouter({ - engine: indexBuilder.getSearchEngine(), - types: indexBuilder.getDocumentTypes(), - discovery, - permissions, - config, - logger, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/search', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/techdocs-backend/dev/index.ts b/plugins/techdocs-backend/dev/index.ts new file mode 100644 index 0000000000..43c75d24df --- /dev/null +++ b/plugins/techdocs-backend/dev/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +backend.add(import('../src/alpha')); +backend.start(); diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index a5a8e0ba76..89e08878b6 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -73,6 +73,7 @@ "winston": "^3.2.1" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/dockerode": "^3.3.0", diff --git a/plugins/techdocs-backend/src/service/standaloneServer.ts b/plugins/techdocs-backend/src/service/standaloneServer.ts deleted file mode 100644 index ce981633ca..0000000000 --- a/plugins/techdocs-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - CacheManager, - createServiceBuilder, - DockerContainerRunner, - HostDiscovery, - UrlReader, -} from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; -import { - DirectoryPreparer, - Generators, - Preparers, - Publisher, - TechdocsGenerator, -} from '@backstage/plugin-techdocs-node'; -import Docker from 'dockerode'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'techdocs-backend' }); - const config = new ConfigReader({ - techdocs: { - publisher: { - type: 'local', - }, - }, - }); - const discovery = HostDiscovery.fromConfig(config); - const mockUrlReader: jest.Mocked = { - readUrl: jest.fn(), - readTree: jest.fn(), - search: jest.fn(), - }; - - logger.debug('Creating application...'); - const preparers = new Preparers(); - const directoryPreparer = DirectoryPreparer.fromConfig(config, { - logger, - reader: mockUrlReader, - }); - preparers.register('dir', directoryPreparer); - - const dockerClient = new Docker(); - const containerRunner = new DockerContainerRunner({ dockerClient }); - - const generators = new Generators(); - const techdocsGenerator = TechdocsGenerator.fromConfig(config, { - logger, - containerRunner, - }); - generators.register('techdocs', techdocsGenerator); - - const publisher = await Publisher.fromConfig(config, { logger, discovery }); - - const cache = CacheManager.fromConfig(config).forPlugin('techdocs'); - - logger.debug('Starting application server...'); - const router = await createRouter({ - preparers, - generators, - logger, - publisher, - config, - discovery, - cache, - }); - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/techdocs', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/user-settings-backend/dev/index.ts b/plugins/user-settings-backend/dev/index.ts new file mode 100644 index 0000000000..31963edc95 --- /dev/null +++ b/plugins/user-settings-backend/dev/index.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; +import { + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { IdentityApi } from '@backstage/plugin-auth-node'; + +const identityMock: IdentityApi = { + async getIdentity({ request }) { + const token = request.headers.authorization?.split(' ')[1]; + return { + identity: { + type: 'user', + ownershipEntityRefs: [], + userEntityRef: token || 'user:default/john_doe', + }, + token: token || 'no-token', + }; + }, +}; + +const backend = createBackend(); +backend.add( + createServiceFactory(() => ({ + service: coreServices.identity, + deps: {}, + async factory() { + return identityMock; + }, + })), +); +backend.add(import('../src/alpha')); +backend.start(); diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index ac5e5d9af2..1016d59256 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -60,6 +60,7 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", diff --git a/plugins/user-settings-backend/src/run.ts b/plugins/user-settings-backend/src/run.ts deleted file mode 100644 index 178de0f786..0000000000 --- a/plugins/user-settings-backend/src/run.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; - -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/user-settings-backend/src/service/standaloneServer.ts b/plugins/user-settings-backend/src/service/standaloneServer.ts deleted file mode 100644 index 48a640636f..0000000000 --- a/plugins/user-settings-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceBuilder, - DatabaseManager, -} from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; -import { IdentityApi } from '@backstage/plugin-auth-node'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { DatabaseUserSettingsStore } from '../database/DatabaseUserSettingsStore'; -import { createRouterInternal } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'storage-backend' }); - - const manager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { client: 'better-sqlite3', connection: ':memory:' }, - }, - }), - ); - const database = manager.forPlugin('user-settings'); - - logger.debug('Starting application server...'); - - const identityMock: IdentityApi = { - async getIdentity({ request }) { - const token = request.headers.authorization?.split(' ')[1]; - return { - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: token || 'user:default/john_doe', - }, - token: token || 'no-token', - }; - }, - }; - - const router = await createRouterInternal({ - userSettingsStore: await DatabaseUserSettingsStore.create({ database }), - identity: identityMock, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/user-settings', router); - - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/yarn.lock b/yarn.lock index 728bac0c68..9b6baf6048 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4351,6 +4351,7 @@ __metadata: dependencies: "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -5268,6 +5269,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend@workspace:plugins/catalog-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-openapi-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" @@ -5587,6 +5589,7 @@ __metadata: resolution: "@backstage/plugin-devtools-backend@workspace:plugins/devtools-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -6331,6 +6334,7 @@ __metadata: resolution: "@backstage/plugin-proxy-backend@workspace:plugins/proxy-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -7016,6 +7020,7 @@ __metadata: resolution: "@backstage/plugin-search-backend@workspace:plugins/search-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-openapi-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -7245,6 +7250,7 @@ __metadata: resolution: "@backstage/plugin-techdocs-backend@workspace:plugins/techdocs-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" @@ -7428,6 +7434,7 @@ __metadata: resolution: "@backstage/plugin-user-settings-backend@workspace:plugins/user-settings-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -9482,6 +9489,7 @@ __metadata: resolution: "@internal/plugin-todo-list-backend@workspace:plugins/example-todo-list-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" From c6c0919ece6951ad8069977b6e661112a6804e5e Mon Sep 17 00:00:00 2001 From: raffi-tamizian Date: Fri, 17 May 2024 13:17:24 +0100 Subject: [PATCH 463/567] Update config to reflect valid option Signed-off-by: raffi-tamizian --- .changeset/cold-seas-end.md | 5 +++++ packages/backend-common/config.d.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/cold-seas-end.md diff --git a/.changeset/cold-seas-end.md b/.changeset/cold-seas-end.md new file mode 100644 index 0000000000..751110a866 --- /dev/null +++ b/.changeset/cold-seas-end.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Updates config.d.ts to reflect valid redis cache config option diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index c6263cde97..ed253dd54d 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -191,6 +191,11 @@ export interface Config { connection: string; /** An optional default TTL (in milliseconds). */ defaultTtl?: number; + /** + * Whether or not [useRedisSets](https://github.com/jaredwray/keyv/tree/main/packages/redis#useredissets) should be configured to this redis cache. + * Defaults to true if unspecified. + */ + useRedisSets?: boolean; } | { store: 'memcache'; From c1745b609f3c0bb7b04879e0a69146671e13de64 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 17 May 2024 14:25:51 +0200 Subject: [PATCH 464/567] Update .changeset/wet-crabs-guess.md Co-authored-by: Patrik Oldsberg Signed-off-by: Camila Belo --- .changeset/wet-crabs-guess.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/wet-crabs-guess.md b/.changeset/wet-crabs-guess.md index e15e56e77d..c8a9282a47 100644 --- a/.changeset/wet-crabs-guess.md +++ b/.changeset/wet-crabs-guess.md @@ -3,4 +3,4 @@ '@backstage/plugin-catalog-backend': patch --- -Start using the `isDatabaseConflictError` helper from the `backend-plugin-api` package in order to avoid dependency with the soon to deprecate `backend-common` package. +Start using the `isDatabaseConflictError` helper from the `@backstage/backend-plugin-api` package in order to avoid dependency with the soon to deprecate `@backstage/backend-common` package. From c09d8df635cff2db4b2e195d5193f0f756cfb0ae Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 17 May 2024 14:26:01 +0200 Subject: [PATCH 465/567] Update .changeset/eighty-kings-dress.md Co-authored-by: Patrik Oldsberg Signed-off-by: Camila Belo --- .changeset/eighty-kings-dress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/eighty-kings-dress.md b/.changeset/eighty-kings-dress.md index f83c2ab2b2..52aab627ab 100644 --- a/.changeset/eighty-kings-dress.md +++ b/.changeset/eighty-kings-dress.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -In preparation to the new backend system stable release, the `isDatabaseConnectionError` helper have been moved to the `backend-plugin-api` package and deprecated from `backend-common`. +In preparation to the new backend system stable release, the `isDatabaseConflictError` helper have been moved to the `@backstage/backend-plugin-api` package and deprecated from `@backstage/backend-common`. From bdec300f080dbf98099763c7a7ff1b130385d9ac Mon Sep 17 00:00:00 2001 From: Raffi Tamizian <3493656+raffitamizian@users.noreply.github.com> Date: Fri, 17 May 2024 13:31:51 +0100 Subject: [PATCH 466/567] Update .changeset/cold-seas-end.md Co-authored-by: Patrik Oldsberg Signed-off-by: Raffi Tamizian <3493656+raffitamizian@users.noreply.github.com> --- .changeset/cold-seas-end.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cold-seas-end.md b/.changeset/cold-seas-end.md index 751110a866..96fa048fbd 100644 --- a/.changeset/cold-seas-end.md +++ b/.changeset/cold-seas-end.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Updates config.d.ts to reflect valid redis cache config option +Updated configuration schema to include the `useRedisSets` cache config option. From 8472011064ff4b568ac908fb778f9dee6d888c35 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 17 May 2024 14:44:27 +0200 Subject: [PATCH 467/567] Update .changeset/cyan-snails-peel.md Co-authored-by: Patrik Oldsberg Signed-off-by: Camila Belo --- .changeset/cyan-snails-peel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/cyan-snails-peel.md b/.changeset/cyan-snails-peel.md index 4bb159b70f..4da79258fb 100644 --- a/.changeset/cyan-snails-peel.md +++ b/.changeset/cyan-snails-peel.md @@ -9,4 +9,4 @@ '@backstage/plugin-app-backend': patch --- -Migrate the `dev` server to use the new backend system. +Updated local development setup. From 45dd173d8d4f1a9ee1e9f87fff7bcc8083ab8321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Jaj=C4=8Devi=C4=87?= Date: Fri, 17 May 2024 15:50:23 +0200 Subject: [PATCH 468/567] Update ibm-apic-backend.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update iconUrl Signed-off-by: Denis Jajčević --- microsite/data/plugins/ibm-apic-backend.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/ibm-apic-backend.yaml b/microsite/data/plugins/ibm-apic-backend.yaml index 9228524a2d..dcb41a1cef 100644 --- a/microsite/data/plugins/ibm-apic-backend.yaml +++ b/microsite/data/plugins/ibm-apic-backend.yaml @@ -5,7 +5,7 @@ authorUrl: https://croz.net/?utm_source=backstage.io&utm_medium=marketplace&utm_ category: API Management description: Bring IBM APIC to Backstage. documentation: https://github.com/croz-ltd/apic-backend-plugin?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=backstage-ibm-apic-backend -iconUrl: https://croz.net/wp-content/uploads/2024/02/croz_large-o.png +iconUrl: https://croz.net/app/uploads/2024/05/apple-touch-icon.png npmPackageName: '@croz/plugin-ibm-apic-backend' tags: - openapic From 0177f7589286bf0aa1918c17f13d844ded32c1c0 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Fri, 17 May 2024 16:17:37 -0400 Subject: [PATCH 469/567] fix: move kubernetes autoscaling to v2 Signed-off-by: Matthew Clarke --- .changeset/stupid-tigers-bake.md | 8 ++ .../dice-roller/dice-roller-manifests.yaml | 2 +- .../src/service/KubernetesFanOutHandler.ts | 2 +- plugins/kubernetes-common/api-report.md | 6 +- .../__fixtures__/hpa-healthy.json | 75 ++++++++--- .../__fixtures__/hpa-maxed-out.json | 75 ++++++++--- .../error-detection/error-detection.test.ts | 6 +- .../src/error-detection/error-detection.ts | 2 +- .../src/error-detection/hpas.ts | 4 +- plugins/kubernetes-common/src/types.ts | 6 +- plugins/kubernetes-react/api-report.md | 4 +- .../src/__fixtures__/1-deployments.json | 123 +++++++++--------- .../src/__fixtures__/1-statefulsets.json | 123 +++++++++--------- .../src/__fixtures__/2-deployments.json | 119 ++++++++--------- .../src/__fixtures__/2-statefulsets.json | 123 +++++++++--------- .../CustomResources/ArgoRollouts/Rollout.tsx | 19 ++- .../DeploymentsAccordions.tsx | 20 ++- .../HorizontalPodAutoscalerDrawer.tsx | 21 ++- .../horizontalpodautoscalers.json | 115 ++++++++-------- .../StatefulSetsAccordions.tsx | 20 ++- plugins/kubernetes-react/src/utils/owner.ts | 6 +- .../src/__fixtures__/1-deployments.json | 123 +++++++++--------- .../src/__fixtures__/1-statefulsets.json | 123 +++++++++--------- .../src/__fixtures__/2-deployments.json | 123 +++++++++--------- .../src/__fixtures__/2-statefulsets.json | 123 +++++++++--------- 25 files changed, 725 insertions(+), 646 deletions(-) create mode 100644 .changeset/stupid-tigers-bake.md diff --git a/.changeset/stupid-tigers-bake.md b/.changeset/stupid-tigers-bake.md new file mode 100644 index 0000000000..8572caa675 --- /dev/null +++ b/.changeset/stupid-tigers-bake.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-common': minor +'@backstage/plugin-kubernetes-react': minor +'@backstage/plugin-kubernetes': minor +--- + +Update kubernetes plugins to use autoscaling/v2 diff --git a/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml b/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml index 8f225e666a..4d564b7552 100644 --- a/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml +++ b/plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml @@ -221,7 +221,7 @@ spec: - containerPort: 80 --- -apiVersion: autoscaling/v1 +apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: dice-roller diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 53b5070e61..54a038fffd 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -103,7 +103,7 @@ export const DEFAULT_OBJECTS: ObjectToFetch[] = [ }, { group: 'autoscaling', - apiVersion: 'v1', + apiVersion: 'v2', plural: 'horizontalpodautoscalers', objectType: 'horizontalpodautoscalers', }, diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 2f46ef90e5..81e9ca28e7 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -14,7 +14,6 @@ import { V1ConfigMap } from '@kubernetes/client-node'; import { V1CronJob } from '@kubernetes/client-node'; import { V1DaemonSet } from '@kubernetes/client-node'; import { V1Deployment } from '@kubernetes/client-node'; -import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { V1Ingress } from '@kubernetes/client-node'; import { V1Job } from '@kubernetes/client-node'; import { V1LimitRange } from '@kubernetes/client-node'; @@ -23,6 +22,7 @@ import { V1ReplicaSet } from '@kubernetes/client-node'; import { V1ResourceQuota } from '@kubernetes/client-node'; import { V1Service } from '@kubernetes/client-node'; import { V1StatefulSet } from '@kubernetes/client-node'; +import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; // @public export const ANNOTATION_KUBERNETES_API_SERVER = 'kubernetes.io/api-server'; @@ -192,7 +192,7 @@ export interface DeploymentResources { // (undocumented) deployments: V1Deployment[]; // (undocumented) - horizontalPodAutoscalers: V1HorizontalPodAutoscaler[]; + horizontalPodAutoscalers: V2HorizontalPodAutoscaler[]; // (undocumented) pods: V1Pod[]; // (undocumented) @@ -294,7 +294,7 @@ export const groupResponses: ( // @public (undocumented) export interface HorizontalPodAutoscalersFetchResponse { // (undocumented) - resources: Array; + resources: Array; // (undocumented) type: 'horizontalpodautoscalers'; } diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json index 23edee5a07..29c74b4839 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json @@ -1,32 +1,77 @@ { + "apiVersion": "autoscaling/v2", + "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2020-09-28T13:28:00.000Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, "name": "dice-roller", "namespace": "default", - "resourceVersion": "698957", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "a70c8a90-5605-4d7d-adea-05cfb8d9d446" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 13, - "desiredReplicas": 14, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json index 4466e7b4b1..29c74b4839 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json @@ -1,32 +1,77 @@ { + "apiVersion": "autoscaling/v2", + "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2020-09-28T13:28:00.000Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, "name": "dice-roller", "namespace": "default", - "resourceVersion": "698957", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "a70c8a90-5605-4d7d-adea-05cfb8d9d446" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 10, - "minReplicas": 5, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 70 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 10, - "currentCPUUtilizationPercentage": 100 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } diff --git a/plugins/kubernetes-common/src/error-detection/error-detection.test.ts b/plugins/kubernetes-common/src/error-detection/error-detection.test.ts index ae4605c3c5..f57226a01c 100644 --- a/plugins/kubernetes-common/src/error-detection/error-detection.test.ts +++ b/plugins/kubernetes-common/src/error-detection/error-detection.test.ts @@ -17,7 +17,7 @@ import { V1Pod, V1Deployment, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, } from '@kubernetes/client-node'; import { detectErrors } from './error-detection'; import * as healthyPod from './__fixtures__/pod.json'; @@ -61,7 +61,7 @@ const oneDeployment = (deployment: V1Deployment): ObjectsByEntityResponse => { }); }; -const oneHpa = (hpa: V1HorizontalPodAutoscaler): ObjectsByEntityResponse => { +const oneHpa = (hpa: V2HorizontalPodAutoscaler): ObjectsByEntityResponse => { return oneItem({ type: 'horizontalpodautoscalers', resources: [hpa], @@ -328,7 +328,7 @@ describe('detectErrors', () => { expect(err1).toStrictEqual({ sourceRef: { - apiGroup: 'autoscaling/v1', + apiGroup: 'autoscaling/v2', kind: 'HorizontalPodAutoscaler', name: 'dice-roller', namespace: 'default', diff --git a/plugins/kubernetes-common/src/error-detection/error-detection.ts b/plugins/kubernetes-common/src/error-detection/error-detection.ts index a3a4d58d9e..6fa9211db4 100644 --- a/plugins/kubernetes-common/src/error-detection/error-detection.ts +++ b/plugins/kubernetes-common/src/error-detection/error-detection.ts @@ -21,7 +21,7 @@ import { detectErrorsInPods } from './pods'; import { detectErrorsInDeployments } from './deployments'; import { detectErrorsInHpa } from './hpas'; import { Deployment } from 'kubernetes-models/apps/v1'; -import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v1'; +import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v2'; import { Pod } from 'kubernetes-models/v1'; /** diff --git a/plugins/kubernetes-common/src/error-detection/hpas.ts b/plugins/kubernetes-common/src/error-detection/hpas.ts index 6f15c119b6..785e6b0e9b 100644 --- a/plugins/kubernetes-common/src/error-detection/hpas.ts +++ b/plugins/kubernetes-common/src/error-detection/hpas.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v1'; +import { HorizontalPodAutoscaler } from 'kubernetes-models/autoscaling/v2'; import { DetectedError, ErrorMapper } from './types'; import { detectErrorsInObjects } from './common'; @@ -35,7 +35,7 @@ const hpaErrorMappers: ErrorMapper[] = [ name: hpa.metadata?.name ?? 'unknown hpa', namespace: hpa.metadata?.namespace ?? 'unknown namespace', kind: 'HorizontalPodAutoscaler', - apiGroup: 'autoscaling/v1', + apiGroup: 'autoscaling/v2', }, occurrenceCount: 1, }, diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index c4bb5fd32f..a63c157948 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -21,7 +21,7 @@ import { V1CronJob, V1DaemonSet, V1Deployment, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, V1Ingress, V1Job, V1LimitRange, @@ -186,7 +186,7 @@ export interface ResourceQuotaFetchResponse { /** @public */ export interface HorizontalPodAutoscalersFetchResponse { type: 'horizontalpodautoscalers'; - resources: Array; + resources: Array; } /** @public */ @@ -282,7 +282,7 @@ export interface DeploymentResources { pods: V1Pod[]; replicaSets: V1ReplicaSet[]; deployments: V1Deployment[]; - horizontalPodAutoscalers: V1HorizontalPodAutoscaler[]; + horizontalPodAutoscalers: V2HorizontalPodAutoscaler[]; } /** @public */ diff --git a/plugins/kubernetes-react/api-report.md b/plugins/kubernetes-react/api-report.md index ccc2e1643d..2d080d110d 100644 --- a/plugins/kubernetes-react/api-report.md +++ b/plugins/kubernetes-react/api-report.md @@ -33,10 +33,10 @@ import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import * as React_3 from 'react'; import { TypeMeta } from '@kubernetes-models/base'; -import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { V1Job } from '@kubernetes/client-node'; import { V1ObjectMeta } from '@kubernetes/client-node'; import { V1Pod } from '@kubernetes/client-node'; +import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { WorkloadsByEntityRequest } from '@backstage/plugin-kubernetes-common'; // @public (undocumented) @@ -285,7 +285,7 @@ export const GroupedResponsesContext: React_2.Context; // @public (undocumented) export const HorizontalPodAutoscalerDrawer: (props: { - hpa: V1HorizontalPodAutoscaler; + hpa: V2HorizontalPodAutoscaler; expanded?: boolean; children?: React_2.ReactNode; }) => React_2.JSX.Element; diff --git a/plugins/kubernetes-react/src/__fixtures__/1-deployments.json b/plugins/kubernetes-react/src/__fixtures__/1-deployments.json index 5ad847dc49..3a9da2a33a 100644 --- a/plugins/kubernetes-react/src/__fixtures__/1-deployments.json +++ b/plugins/kubernetes-react/src/__fixtures__/1-deployments.json @@ -2826,85 +2826,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json b/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json index 6c04774ff1..5d9b56300a 100644 --- a/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json +++ b/plugins/kubernetes-react/src/__fixtures__/1-statefulsets.json @@ -2827,85 +2827,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/__fixtures__/2-deployments.json b/plugins/kubernetes-react/src/__fixtures__/2-deployments.json index f5efdbf1cb..829636b6f3 100644 --- a/plugins/kubernetes-react/src/__fixtures__/2-deployments.json +++ b/plugins/kubernetes-react/src/__fixtures__/2-deployments.json @@ -4434,85 +4434,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { "maxReplicas": 15, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], "minReplicas": 10, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 30, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json b/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json index 0a8c26c198..c45b1070d6 100644 --- a/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json +++ b/plugins/kubernetes-react/src/__fixtures__/2-statefulsets.json @@ -4436,85 +4436,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx index 65749c2ea2..8e62b2606f 100644 --- a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx +++ b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.tsx @@ -21,7 +21,7 @@ import AccordionSummary from '@material-ui/core/AccordionSummary'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { V1Pod, V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; +import { V1Pod, V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { PodsTable } from '../../Pods'; import { HorizontalPodAutoscalerDrawer } from '../../HorizontalPodAutoscalers'; import { RolloutDrawer } from './RolloutDrawer'; @@ -50,7 +50,7 @@ type RolloutAccordionProps = { rollout: any; ownedPods: V1Pod[]; defaultExpanded?: boolean; - matchingHpa?: V1HorizontalPodAutoscaler; + matchingHpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -58,7 +58,7 @@ type RolloutSummaryProps = { rollout: any; numberOfCurrentPods: number; numberOfPodsWithErrors: number; - hpa?: V1HorizontalPodAutoscaler; + hpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -93,6 +93,13 @@ const RolloutSummary = ({ (p: any) => p.reason === 'CanaryPauseStep', )?.startTime; const abortedMessage = findAbortedMessage(rollout); + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; return ( - current CPU usage:{' '} - {hpa.status?.currentCPUUtilizationPercentage ?? '?'}% + current CPU usage: {cpuUtil ?? '?'}% - target CPU usage:{' '} - {hpa.spec?.targetCPUUtilizationPercentage ?? '?'}% + target CPU usage: {specCpuUtil ?? '?'}% diff --git a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx index 3d048f22df..8cfc79a8ae 100644 --- a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentsAccordions.tsx @@ -24,7 +24,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { V1Deployment, V1Pod, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, } from '@kubernetes/client-node'; import { PodsTable } from '../Pods'; import { DeploymentDrawer } from './DeploymentDrawer'; @@ -47,7 +47,7 @@ type DeploymentsAccordionsProps = { type DeploymentAccordionProps = { deployment: V1Deployment; ownedPods: V1Pod[]; - matchingHpa?: V1HorizontalPodAutoscaler; + matchingHpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -55,7 +55,7 @@ type DeploymentSummaryProps = { deployment: V1Deployment; numberOfCurrentPods: number; numberOfPodsWithErrors: number; - hpa?: V1HorizontalPodAutoscaler; + hpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -65,6 +65,14 @@ const DeploymentSummary = ({ numberOfPodsWithErrors, hpa, }: DeploymentSummaryProps) => { + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; + return ( - current CPU usage:{' '} - {hpa.status?.currentCPUUtilizationPercentage ?? '?'}% + current CPU usage: {cpuUtil ?? '?'}% - target CPU usage:{' '} - {hpa.spec?.targetCPUUtilizationPercentage ?? '?'}% + target CPU usage: {specCpuUtil ?? '?'}% diff --git a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx index dba942155c..0194bfca2a 100644 --- a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx +++ b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.tsx @@ -15,32 +15,39 @@ */ import React from 'react'; -import { V1HorizontalPodAutoscaler } from '@kubernetes/client-node'; +import { V2HorizontalPodAutoscaler } from '@kubernetes/client-node'; import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; /** @public */ export const HorizontalPodAutoscalerDrawer = (props: { - hpa: V1HorizontalPodAutoscaler; + hpa: V2HorizontalPodAutoscaler; expanded?: boolean; children?: React.ReactNode; }) => { const { hpa, expanded, children } = props; + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; + return ( { + renderObject={(hpaObject: V2HorizontalPodAutoscaler) => { return { - targetCPUUtilizationPercentage: - hpaObject.spec?.targetCPUUtilizationPercentage, - currentCPUUtilizationPercentage: - hpaObject.status?.currentCPUUtilizationPercentage, + targetCPUUtilizationPercentage: specCpuUtil, + currentCPUUtilizationPercentage: cpuUtil, minReplicas: hpaObject.spec?.minReplicas, maxReplicas: hpaObject.spec?.maxReplicas, currentReplicas: hpaObject.status?.currentReplicas, desiredReplicas: hpaObject.status?.desiredReplicas, + lastScaleTime: hpa?.status?.lastScaleTime, }; }} > diff --git a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json index 6afdda48ed..94cc8a840d 100644 --- a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json +++ b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/__fixtures__/horizontalpodautoscalers.json @@ -1,82 +1,79 @@ [ { + "apiVersion": "autoscaling/v2", + "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2020-09-28T13:28:15Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: unable to fetch metrics from resource metrics API: the server could not find the requested resource (get pods.metrics.k8s.io)\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2020-09-28T13:28:00.000Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2020-09-28T13:28:15.000Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl", - "operation": "Update", - "time": "2020-09-28T13:28:21.000Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "698957", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "a70c8a90-5605-4d7d-adea-05cfb8d9d446" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { "maxReplicas": 15, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 30, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], "minReplicas": 10, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 50, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], "currentReplicas": 13, "desiredReplicas": 14, - "currentCPUUtilizationPercentage": 30 + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx index 00f08c3c98..80fff510e7 100644 --- a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx +++ b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetsAccordions.tsx @@ -23,7 +23,7 @@ import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { V1Pod, - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, V1StatefulSet, } from '@kubernetes/client-node'; import { PodsTable } from '../Pods'; @@ -44,7 +44,7 @@ type StatefulSetsAccordionsProps = { type StatefulSetAccordionProps = { statefulset: V1StatefulSet; ownedPods: V1Pod[]; - matchingHpa?: V1HorizontalPodAutoscaler; + matchingHpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -52,7 +52,7 @@ type StatefulSetSummaryProps = { statefulset: V1StatefulSet; numberOfCurrentPods: number; numberOfPodsWithErrors: number; - hpa?: V1HorizontalPodAutoscaler; + hpa?: V2HorizontalPodAutoscaler; children?: React.ReactNode; }; @@ -62,6 +62,14 @@ const StatefulSetSummary = ({ numberOfPodsWithErrors, hpa, }: StatefulSetSummaryProps) => { + const specCpuUtil = hpa?.spec?.metrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.target.averageUtilization; + + const cpuUtil = hpa?.status?.currentMetrics?.find( + metric => metric.type === 'Resource' && metric.resource?.name === 'cpu', + )?.resource?.current.averageUtilization; + return ( - current CPU usage:{' '} - {hpa.status?.currentCPUUtilizationPercentage ?? '?'}% + current CPU usage: {cpuUtil ?? '?'}% - target CPU usage:{' '} - {hpa.spec?.targetCPUUtilizationPercentage ?? '?'}% + target CPU usage: {specCpuUtil ?? '?'}% diff --git a/plugins/kubernetes-react/src/utils/owner.ts b/plugins/kubernetes-react/src/utils/owner.ts index 1c1d8089a3..bd0cd39677 100644 --- a/plugins/kubernetes-react/src/utils/owner.ts +++ b/plugins/kubernetes-react/src/utils/owner.ts @@ -16,7 +16,7 @@ import { V1ObjectMeta } from '@kubernetes/client-node/dist/gen/model/v1ObjectMeta'; import { - V1HorizontalPodAutoscaler, + V2HorizontalPodAutoscaler, V1Pod, V1ReplicaSet, } from '@kubernetes/client-node'; @@ -62,8 +62,8 @@ interface ResourceRef { export const getMatchingHpa = ( owner: ResourceRef, - hpas: V1HorizontalPodAutoscaler[], -): V1HorizontalPodAutoscaler | undefined => { + hpas: V2HorizontalPodAutoscaler[], +): V2HorizontalPodAutoscaler | undefined => { return hpas.find(hpa => { return ( (hpa.spec?.scaleTargetRef?.kind ?? '').toLocaleLowerCase('en-US') === diff --git a/plugins/kubernetes/src/__fixtures__/1-deployments.json b/plugins/kubernetes/src/__fixtures__/1-deployments.json index 5ad847dc49..3a9da2a33a 100644 --- a/plugins/kubernetes/src/__fixtures__/1-deployments.json +++ b/plugins/kubernetes/src/__fixtures__/1-deployments.json @@ -2826,85 +2826,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes/src/__fixtures__/1-statefulsets.json b/plugins/kubernetes/src/__fixtures__/1-statefulsets.json index 6c04774ff1..5d9b56300a 100644 --- a/plugins/kubernetes/src/__fixtures__/1-statefulsets.json +++ b/plugins/kubernetes/src/__fixtures__/1-statefulsets.json @@ -2827,85 +2827,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes/src/__fixtures__/2-deployments.json b/plugins/kubernetes/src/__fixtures__/2-deployments.json index f5efdbf1cb..c8209e9f0a 100644 --- a/plugins/kubernetes/src/__fixtures__/2-deployments.json +++ b/plugins/kubernetes/src/__fixtures__/2-deployments.json @@ -4434,85 +4434,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] diff --git a/plugins/kubernetes/src/__fixtures__/2-statefulsets.json b/plugins/kubernetes/src/__fixtures__/2-statefulsets.json index 0a8c26c198..c45b1070d6 100644 --- a/plugins/kubernetes/src/__fixtures__/2-statefulsets.json +++ b/plugins/kubernetes/src/__fixtures__/2-statefulsets.json @@ -4436,85 +4436,80 @@ ], "horizontalPodAutoscalers": [ { - "apiVersion": "autoscaling/v1", + "apiVersion": "autoscaling/v2", "kind": "HorizontalPodAutoscaler", "metadata": { - "annotations": { - "autoscaling.alpha.kubernetes.io/conditions": "[{\"type\":\"AbleToScale\",\"status\":\"True\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"SucceededGetScale\",\"message\":\"the HPA controller was able to get the target's current scale\"},{\"type\":\"ScalingActive\",\"status\":\"False\",\"lastTransitionTime\":\"2021-01-05T10:26:04Z\",\"reason\":\"FailedGetResourceMetric\",\"message\":\"the HPA was unable to compute the replica count: unable to get metrics for resource cpu: no metrics returned from resource metrics API\"}]", - "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"autoscaling/v1\",\"kind\":\"HorizontalPodAutoscaler\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"maxReplicas\":15,\"minReplicas\":10,\"scaleTargetRef\":{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"dice-roller\"},\"targetCPUUtilizationPercentage\":50}}\n" - }, - "creationTimestamp": "2021-01-05T10:25:48Z", + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", "labels": { "backstage.io/kubernetes-id": "dice-roller" }, - "managedFields": [ - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - ".": {}, - "f:kubectl.kubernetes.io/last-applied-configuration": {} - }, - "f:labels": { - ".": {}, - "f:backstage.io/kubernetes-id": {} - } - }, - "f:spec": { - "f:maxReplicas": {}, - "f:minReplicas": {}, - "f:scaleTargetRef": { - "f:apiVersion": {}, - "f:kind": {}, - "f:name": {} - }, - "f:targetCPUUtilizationPercentage": {} - } - }, - "manager": "kubectl-client-side-apply", - "operation": "Update", - "time": "2021-01-05T10:25:48Z" - }, - { - "apiVersion": "autoscaling/v1", - "fieldsType": "FieldsV1", - "fieldsV1": { - "f:metadata": { - "f:annotations": { - "f:autoscaling.alpha.kubernetes.io/conditions": {} - } - }, - "f:status": { - "f:currentReplicas": {} - } - }, - "manager": "kube-controller-manager", - "operation": "Update", - "time": "2021-01-05T10:26:04Z" - } - ], "name": "dice-roller", "namespace": "default", - "resourceVersion": "598", - "selfLink": "/apis/autoscaling/v1/namespaces/default/horizontalpodautoscalers/dice-roller", - "uid": "dd7c5329-567c-43c2-b159-756808d90a8e" + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 15, - "minReplicas": 10, + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, "scaleTargetRef": { "apiVersion": "apps/v1", "kind": "Deployment", "name": "dice-roller" - }, - "targetCPUUtilizationPercentage": 50 + } }, "status": { - "currentReplicas": 10, - "desiredReplicas": 0, - "currentCPUUtilizationPercentage": 30 + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" } } ] From b8507a1487208fa9e1c6e749e745f681229c5de6 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Fri, 17 May 2024 16:20:02 -0400 Subject: [PATCH 470/567] fix: changeset Signed-off-by: Matthew Clarke --- .changeset/stupid-tigers-bake.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/stupid-tigers-bake.md b/.changeset/stupid-tigers-bake.md index 8572caa675..a4579ab05a 100644 --- a/.changeset/stupid-tigers-bake.md +++ b/.changeset/stupid-tigers-bake.md @@ -2,7 +2,6 @@ '@backstage/plugin-kubernetes-backend': minor '@backstage/plugin-kubernetes-common': minor '@backstage/plugin-kubernetes-react': minor -'@backstage/plugin-kubernetes': minor --- Update kubernetes plugins to use autoscaling/v2 From 0e8c00cfa6f96e84c551290a37a8cff7f11b9d76 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Fri, 17 May 2024 16:28:03 -0400 Subject: [PATCH 471/567] fix: missed tests Signed-off-by: Matthew Clarke --- .../src/error-detection/__fixtures__/hpa-healthy.json | 2 +- .../src/error-detection/__fixtures__/hpa-maxed-out.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json index 29c74b4839..2fc73a5ee3 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-healthy.json @@ -13,7 +13,7 @@ "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 2, + "maxReplicas": 10, "metrics": [ { "resource": { diff --git a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json index 29c74b4839..2360e14ca0 100644 --- a/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json +++ b/plugins/kubernetes-common/src/error-detection/__fixtures__/hpa-maxed-out.json @@ -13,7 +13,7 @@ "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" }, "spec": { - "maxReplicas": 2, + "maxReplicas": 10, "metrics": [ { "resource": { @@ -70,8 +70,8 @@ "type": "Resource" } ], - "currentReplicas": 2, - "desiredReplicas": 2, + "currentReplicas": 10, + "desiredReplicas": 10, "lastScaleTime": "2024-02-13T20:14:23Z" } } From a322d76037c9f1b7d60422e4615f227d5a35db60 Mon Sep 17 00:00:00 2001 From: Eric Roberson Date: Sat, 18 May 2024 09:45:16 -0700 Subject: [PATCH 472/567] chore(catalog): aboutcard tests typo fix typo fix Signed-off-by: Eric Roberson --- plugins/catalog/src/components/AboutCard/AboutCard.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index f90942dcd8..45058c88ed 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -546,7 +546,7 @@ describe('', () => { ).not.toBeInTheDocument(); }); - it('renders techdocs lin when 3rdparty', async () => { + it('renders techdocs link when 3rdparty', async () => { const entity = { apiVersion: 'v1', kind: 'Component', From e49d0fd53bceebda73bd7421f82b5434629dc6c1 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sat, 18 May 2024 21:14:41 +0200 Subject: [PATCH 473/567] refactor: apply review suggestions Signed-off-by: Camila Belo --- .changeset/late-ants-impress.md | 2 +- .changeset/old-trees-check.md | 2 +- .changeset/olive-mangos-tickle.md | 2 +- packages/backend-app-api/api-report.md | 16 ------- .../backend-app-api/src/logging/VoidLogger.ts | 44 ------------------- packages/backend-app-api/src/logging/index.ts | 1 - .../PgSearchEngine/PgSearchEngineIndexer.ts | 10 ++--- 7 files changed, 7 insertions(+), 70 deletions(-) delete mode 100644 packages/backend-app-api/src/logging/VoidLogger.ts diff --git a/.changeset/late-ants-impress.md b/.changeset/late-ants-impress.md index c694cea7c4..44b2cbefb3 100644 --- a/.changeset/late-ants-impress.md +++ b/.changeset/late-ants-impress.md @@ -8,4 +8,4 @@ '@backstage/plugin-events-node': patch --- -Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-deprecate `backend-common` package. +Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. diff --git a/.changeset/old-trees-check.md b/.changeset/old-trees-check.md index 4c259bf4d7..b4d745ffb5 100644 --- a/.changeset/old-trees-check.md +++ b/.changeset/old-trees-check.md @@ -2,4 +2,4 @@ '@backstage/backend-tasks': patch --- -Deprecate the legacy `TaskScheduler.fromConfig` method and stop using the `getVoidlogger` in tests files to reduce the dependecy on the soon-to-deprecate `backstage-common` package. +Deprecate the legacy `TaskScheduler.fromConfig` method and stop using the `getVoidlogger` in tests files to reduce the dependency on the soon-to-deprecate `backstage-common` package. diff --git a/.changeset/olive-mangos-tickle.md b/.changeset/olive-mangos-tickle.md index d28d250ca3..6033ec3ed6 100644 --- a/.changeset/olive-mangos-tickle.md +++ b/.changeset/olive-mangos-tickle.md @@ -2,4 +2,4 @@ '@backstage/backend-app-api': patch --- -Export a new `VoidLogger` implementation and stop using `getVoidLogger` in tests to reduce the dependecy on the soon-to-deprecate `backstage-common` package. +Stop using `getVoidLogger` in tests to reduce the dependency on the soon-to-deprecate `backstage-common` package. diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 1d681c308b..5720c1c60f 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -347,22 +347,6 @@ export const userInfoServiceFactory: () => ServiceFactory< 'plugin' >; -// @public -export class VoidLogger implements RootLoggerService { - // (undocumented) - child(_meta: JsonObject): LoggerService; - // (undocumented) - static create(): VoidLogger; - // (undocumented) - debug(_message: string, _meta?: JsonObject): void; - // (undocumented) - error(_message: string, _meta?: JsonObject): void; - // (undocumented) - info(_message: string, _meta?: JsonObject): void; - // (undocumented) - warn(_message: string, _meta?: JsonObject): void; -} - // @public export class WinstonLogger implements RootLoggerService { // (undocumented) diff --git a/packages/backend-app-api/src/logging/VoidLogger.ts b/packages/backend-app-api/src/logging/VoidLogger.ts deleted file mode 100644 index 0f5acba07f..0000000000 --- a/packages/backend-app-api/src/logging/VoidLogger.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - LoggerService, - RootLoggerService, -} from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; - -/** - * An empty {@link @backstage/backend-plugin-api#LoggerService} implementation. - * - * @public - */ -export class VoidLogger implements RootLoggerService { - static create(): VoidLogger { - return new VoidLogger(); - } - - error(_message: string, _meta?: JsonObject): void {} - - warn(_message: string, _meta?: JsonObject): void {} - - info(_message: string, _meta?: JsonObject): void {} - - debug(_message: string, _meta?: JsonObject): void {} - - child(_meta: JsonObject): LoggerService { - return new VoidLogger(); - } -} diff --git a/packages/backend-app-api/src/logging/index.ts b/packages/backend-app-api/src/logging/index.ts index a7162553e3..14fe33f898 100644 --- a/packages/backend-app-api/src/logging/index.ts +++ b/packages/backend-app-api/src/logging/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export { VoidLogger } from './VoidLogger'; export { WinstonLogger } from './WinstonLogger'; export type { WinstonLoggerOptions } from './WinstonLogger'; diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts index 93b699fc74..ab5ae8be54 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngineIndexer.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { loggerToWinstonLogger } from '@backstage/backend-common'; import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { Knex } from 'knex'; import { Logger } from 'winston'; import { DatabaseStore } from '../database'; -import { VoidLogger } from '@backstage/backend-app-api'; /** @public */ export type PgSearchEngineIndexerOptions = { @@ -32,7 +30,7 @@ export type PgSearchEngineIndexerOptions = { /** @public */ export class PgSearchEngineIndexer extends BatchSearchEngineIndexer { - private logger: Logger; + private logger?: Logger; private store: DatabaseStore; private type: string; private tx: Knex.Transaction | undefined; @@ -42,7 +40,7 @@ export class PgSearchEngineIndexer extends BatchSearchEngineIndexer { super({ batchSize: options.batchSize }); this.store = options.databaseStore; this.type = options.type; - this.logger = options.logger || loggerToWinstonLogger(VoidLogger.create()); + this.logger = options.logger; } async initialize(): Promise { @@ -61,7 +59,7 @@ export class PgSearchEngineIndexer extends BatchSearchEngineIndexer { this.numRecords += documents.length; const refs = [...new Set(documents.map(d => d.authorization?.resourceRef))]; - this.logger.debug( + this.logger?.debug( `Attempting to index the following entities: ${refs.toString()}`, ); @@ -80,7 +78,7 @@ export class PgSearchEngineIndexer extends BatchSearchEngineIndexer { // and do not continue. This ensures that collators that return empty sets // of documents do not cause the index to be deleted. if (this.numRecords === 0) { - this.logger.warn( + this.logger?.warn( `Index for ${this.type} was not replaced: indexer received 0 documents`, ); this.tx!.rollback!(); From 42593d3c0301aae199566e58506fcc294de6850a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 19 May 2024 13:28:23 +0200 Subject: [PATCH 474/567] microsite: switch feedback rating mode to stars Signed-off-by: Patrik Oldsberg --- microsite/docusaurus.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index b7b98bf9e5..e697c5024f 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -181,6 +181,7 @@ const config: Config = { hideIcon: true, customFont: true, buttonStyle: 'dark', + ratingMode: 'stars', }, ], ], From 1b0ed555c6986addb2baa22cebe9ff329db354a7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 20 May 2024 12:56:47 +0200 Subject: [PATCH 475/567] Apply suggestions from code review Signed-off-by: Patrik Oldsberg --- beps/0007-docs-personas-framework-portal/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beps/0007-docs-personas-framework-portal/README.md b/beps/0007-docs-personas-framework-portal/README.md index b18e4896ec..07468b7c4a 100644 --- a/beps/0007-docs-personas-framework-portal/README.md +++ b/beps/0007-docs-personas-framework-portal/README.md @@ -1,12 +1,12 @@ --- -title: Enhancing Backstage Documentation, Personas, Framework, and Developer Portal +title: Improved Backstage Documentation with Personas status: provisional authors: - '@waldirmontoya25' - '@aramissennyeyd' owners: project-areas: - - Documentation + - core creation-date: 2024-03-18 --- @@ -67,7 +67,7 @@ The proposed restructuring of the Backstage documentation revolves around two co 2. Defining the personas participating in Backstage adoption journeys to improve documentation navigation. The identified personas are: - - **User**: A person who uses Backstage to find information, use plugins, and consume the developer portal. + - **End User**: A person who uses Backstage to find information, use plugins, and consume the developer portal. - **Administrator/Operator**: A person who configures, secures, and deploys the developer portal, manages plugins, and oversees the general administration of the developer portal. - **Integrator/Builder**: A person who builds plugins, customizes the code and design, and creates custom-built developer portals based on the Backstage framework. This includes developers and designers and anyone adding new functionality to their own Backstage instance. - **Product Manager/Business stakeholders**: A person who defines the strategy for adopting Backstage, identifies use cases, communicates the value proposition for adopting Backstage and connects the developer portal to the business strategy. From d4218dc992e103e0ec15a9cf462fdd9b46dcc43e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 20 May 2024 12:58:04 +0200 Subject: [PATCH 476/567] beps: move docs to 0008 Signed-off-by: Patrik Oldsberg --- .../README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename beps/{0007-docs-personas-framework-portal => 0008-docs-personas-framework-portal}/README.md (100%) diff --git a/beps/0007-docs-personas-framework-portal/README.md b/beps/0008-docs-personas-framework-portal/README.md similarity index 100% rename from beps/0007-docs-personas-framework-portal/README.md rename to beps/0008-docs-personas-framework-portal/README.md From 07a789b8c5f0c0fc151b8bb342af810027023249 Mon Sep 17 00:00:00 2001 From: Yaron Dayagi Date: Sun, 12 May 2024 19:14:29 +0300 Subject: [PATCH 477/567] feat: add notifications filtering by processors Signed-off-by: Yaron Dayagi --- .changeset/large-months-decide.md | 5 ++ .changeset/polite-otters-talk.md | 5 ++ .changeset/wild-ears-walk.md | 5 ++ .../config.d.ts | 15 ++++++ .../processor/NotificationsEmailProcessor.ts | 36 +++++++++++++- .../src/service/router.ts | 49 +++++++++++++++++-- plugins/notifications-node/api-report.md | 9 ++++ plugins/notifications-node/src/extensions.ts | 19 ++++++- 8 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 .changeset/large-months-decide.md create mode 100644 .changeset/polite-otters-talk.md create mode 100644 .changeset/wild-ears-walk.md diff --git a/.changeset/large-months-decide.md b/.changeset/large-months-decide.md new file mode 100644 index 0000000000..05d5086794 --- /dev/null +++ b/.changeset/large-months-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-node': major +--- + +add notifications filtering by processors diff --git a/.changeset/polite-otters-talk.md b/.changeset/polite-otters-talk.md new file mode 100644 index 0000000000..e56b935c1e --- /dev/null +++ b/.changeset/polite-otters-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-email': minor +--- + +add notification filters diff --git a/.changeset/wild-ears-walk.md b/.changeset/wild-ears-walk.md new file mode 100644 index 0000000000..e6762b82ee --- /dev/null +++ b/.changeset/wild-ears-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': major +--- + +adding filtering of notifications by processors diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index db49d0016a..92f3385d1b 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -15,6 +15,7 @@ */ import { HumanDuration } from '@backstage/types'; +import { NotificationSeverity } from '@backstage/plugin-notifications-common'; export interface Config { /** @@ -117,6 +118,20 @@ export interface Config { */ ttl?: HumanDuration; }; + filter?: { + /** + * Minimum severity. A notification with lower severity will not be emailed + */ + minSeverity?: NotificationSeverity; + /** + * Maximum severity. A notification with higher severity will not be emailed + */ + maxSeverity?: NotificationSeverity; + /** + * A notification who's topic is in this array will not be emailed + */ + excludedTopics?: string[]; + }; }; }; }; diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index c06d28f903..a78ebd3260 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -15,6 +15,7 @@ */ import { NotificationProcessor, + NotificationProcessorFilters, NotificationSendOptions, } from '@backstage/plugin-notifications-node'; import { @@ -28,7 +29,11 @@ import { CATALOG_FILTER_EXISTS, CatalogClient, } from '@backstage/catalog-client'; -import { Notification } from '@backstage/plugin-notifications-common'; +import { + Notification, + notificationSeverities, + NotificationSeverity, +} from '@backstage/plugin-notifications-common'; import { createSendmailTransport, createSesTransport, @@ -51,6 +56,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly concurrencyLimit: number; private readonly throttleInterval: number; private readonly frontendBaseUrl: string; + private readonly filter: NotificationProcessorFilters; constructor( private readonly logger: LoggerService, @@ -80,6 +86,30 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) : 3_600_000; this.frontendBaseUrl = config.getString('app.baseUrl'); + this.filter = {}; + const minSeverity = emailProcessorConfig.getOptionalString( + 'filter.minSeverity', + ) as NotificationSeverity; + if (minSeverity) { + if (notificationSeverities.includes(minSeverity)) { + this.filter.minSeverity = minSeverity; + } else { + throw new Error(`Invalid minSeverity: ${minSeverity}`); + } + } + const maxSeverity = emailProcessorConfig.getOptionalString( + 'filter.maxSeverity', + ) as NotificationSeverity; + if (maxSeverity) { + if (notificationSeverities.includes(maxSeverity)) { + this.filter.maxSeverity = maxSeverity; + } else { + throw new Error(`Invalid maxSeverity: ${maxSeverity}`); + } + } + this.filter.excludedTopics = emailProcessorConfig.getOptionalStringArray( + 'filter.excludedTopics', + ); } private async getTransporter() { @@ -312,4 +342,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor { await this.sendTemplateEmail(notification, emails); } + + getNotificationFilters(): NotificationProcessorFilters { + return this.filter; + } } diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 585b1ad104..bb5c9e06ac 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -48,7 +48,9 @@ import { SignalsService } from '@backstage/plugin-signals-node'; import { NewNotificationSignal, Notification, + NotificationPayload, NotificationReadSignal, + notificationSeverities, NotificationStatus, } from '@backstage/plugin-notifications-common'; import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams'; @@ -177,9 +179,46 @@ export async function createRouter( return users; }; - const processOptions = async (opts: NotificationSendOptions) => { - let ret = opts; + const filterProcessors = (payload: NotificationPayload) => { + const result: NotificationProcessor[] = []; + for (const processor of processors) { + if (processor.getNotificationFilters) { + const filters = processor.getNotificationFilters(); + if (filters.minSeverity) { + if ( + notificationSeverities.indexOf(payload.severity ?? 'normal') > + notificationSeverities.indexOf(filters.minSeverity) + ) { + continue; + } + } + + if (filters.maxSeverity) { + if ( + notificationSeverities.indexOf(payload.severity ?? 'normal') < + notificationSeverities.indexOf(filters.maxSeverity) + ) { + continue; + } + } + + if (filters.excludedTopics && payload.topic) { + if (filters.excludedTopics.includes(payload.topic)) { + continue; + } + } + } + result.push(processor); + } + + return result; + }; + + const processOptions = async (opts: NotificationSendOptions) => { + const filtered = filterProcessors(opts.payload); + let ret = opts; + for (const processor of filtered) { try { ret = processor.processOptions ? await processor.processOptions(ret) @@ -197,8 +236,9 @@ export async function createRouter( notification: Notification, opts: NotificationSendOptions, ) => { + const filtered = filterProcessors(notification.payload); let ret = notification; - for (const processor of processors) { + for (const processor of filtered) { try { ret = processor.preProcess ? await processor.preProcess(ret, opts) @@ -216,7 +256,8 @@ export async function createRouter( notification: Notification, opts: NotificationSendOptions, ) => { - for (const processor of processors) { + const filtered = filterProcessors(notification.payload); + for (const processor of filtered) { if (processor.postProcess) { try { await processor.postProcess(notification, opts); diff --git a/plugins/notifications-node/api-report.md b/plugins/notifications-node/api-report.md index 1914b562d1..27e6422f8a 100644 --- a/plugins/notifications-node/api-report.md +++ b/plugins/notifications-node/api-report.md @@ -8,6 +8,7 @@ import { DiscoveryService } from '@backstage/backend-plugin-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; import { NotificationPayload } from '@backstage/plugin-notifications-common'; +import { NotificationSeverity } from '@backstage/plugin-notifications-common'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @public (undocumented) @@ -23,6 +24,7 @@ export class DefaultNotificationService implements NotificationService { // @public export interface NotificationProcessor { getName(): string; + getNotificationFilters?(): NotificationProcessorFilters; postProcess?( notification: Notification_2, options: NotificationSendOptions, @@ -36,6 +38,13 @@ export interface NotificationProcessor { ): Promise; } +// @public (undocumented) +export type NotificationProcessorFilters = { + minSeverity?: NotificationSeverity; + maxSeverity?: NotificationSeverity; + excludedTopics?: string[]; +}; + // @public (undocumented) export type NotificationRecipients = | { diff --git a/plugins/notifications-node/src/extensions.ts b/plugins/notifications-node/src/extensions.ts index 6461c3dc89..7adb6caf50 100644 --- a/plugins/notifications-node/src/extensions.ts +++ b/plugins/notifications-node/src/extensions.ts @@ -14,7 +14,10 @@ * limitations under the License. */ import { createExtensionPoint } from '@backstage/backend-plugin-api'; -import { Notification } from '@backstage/plugin-notifications-common'; +import { + Notification, + NotificationSeverity, +} from '@backstage/plugin-notifications-common'; import { NotificationSendOptions } from './service'; /** @@ -90,6 +93,11 @@ export interface NotificationProcessor { notification: Notification, options: NotificationSendOptions, ): Promise; + + /** + * notification filters are used to call the processor only in certain conditions + */ + getNotificationFilters?(): NotificationProcessorFilters; } /** @@ -108,3 +116,12 @@ export const notificationsProcessingExtensionPoint = createExtensionPoint({ id: 'notifications.processing', }); + +/** + * @public + */ +export type NotificationProcessorFilters = { + minSeverity?: NotificationSeverity; + maxSeverity?: NotificationSeverity; + excludedTopics?: string[]; +}; From 03afcf1db50d156536c251d0804a4d1a6fe9d689 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 20 May 2024 22:24:11 +0200 Subject: [PATCH 478/567] fix: remove custom fallback Signed-off-by: ElaineDeMattosSilvaB --- .../src/__testUtils__/handlers.ts | 2 +- .../src/__testUtils__/mocks.ts | 115 +++++++++++++++++- .../GitlabDiscoveryEntityProvider.test.ts | 27 ++-- .../GitlabDiscoveryEntityProvider.ts | 8 +- 4 files changed, 123 insertions(+), 29 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts index 00d4994e94..f2dcac13cd 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts @@ -165,7 +165,7 @@ const httpProjectCatalogDynamic = all_projects_response.map(project => { `${apiBaseUrl}/projects/${path}/repository/files/catalog-info.yaml`, (req, res, ctx) => { const branch = req.url.searchParams.get('ref'); - if (branch === project.default_branch) { + if (branch === (project.default_branch || 'main' || 'develop')) { return res(ctx.status(200)); } return res(ctx.status(404, 'Not Found')); diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts index 5a078ab689..279da0dfc0 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts @@ -190,6 +190,33 @@ export const config_single_integration_branch: MockObject = { }, }; +export const config_single_integration_specific_branch: MockObject = { + integrations: { + gitlab: [ + { + host: 'example.com', + apiBaseUrl: 'https://example.com/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'example.com', + group: 'group1', + branch: 'develop', + skipForkedRepos: false, + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, +}; export const config_single_integration_group: MockObject = { integrations: { gitlab: [ @@ -234,7 +261,7 @@ export const config_fallbackBranch_branch: MockObject = { 'test-id': { host: 'example.com', group: 'group1', - fallbackBranch: 'staging', + fallbackBranch: 'main', skipForkedRepos: false, schedule: { frequency: 'PT30M', @@ -634,7 +661,7 @@ export const all_projects_response: GitLabProject[] = [ web_url: 'https://example.com/group1/test-repo5-staging', path_with_namespace: 'group1/test-repo5-staging', }, - // diffrent group + // different group { id: 6, description: 'Project Six Description', @@ -646,6 +673,17 @@ export const all_projects_response: GitLabProject[] = [ web_url: 'https://example.com/group1/test-repo6', path_with_namespace: 'awesome-group/test-repo6', }, + // no default branch + { + id: 7, + description: 'Project Seven Description', + name: 'test-repo7', + path: 'test-repo7', + archived: false, + last_activity_at: new Date().toString(), + web_url: 'https://example.com/group1/test-repo7', + path_with_namespace: 'group1/test-repo7', + }, ]; export const all_users_response: GitLabUser[] = [ @@ -1299,9 +1337,43 @@ export const push_modif_event: EventParams = { /** * Expected Backstage entities */ -export const expected_location_entities: MockObject[] = + +// includes only projects that have a default branch (for when the branch and default branch were not set in the config) +export const expected_location_entities_default_branch: MockObject[] = + all_projects_response + .filter(project => project.default_branch) + .map(project => { + const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${project.default_branch}/catalog-info.yaml`; + + return { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${targetUrl}`, + 'backstage.io/managed-by-origin-location': `url:${targetUrl}`, + }, + name: locationSpecToMetadataName({ + target: targetUrl, + type: 'url', + }), + }, + spec: { + presence: 'optional', + target: targetUrl, + type: 'url', + }, + }, + locationKey: 'GitlabDiscoveryEntityProvider:test-id', + }; + }); + +// includes every GitLab project that has a default branch and the fallback declared in the config +export const expected_location_entities_fallback_branch: MockObject[] = all_projects_response.map(project => { - const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${project.default_branch}/catalog-info.yaml`; + const branch = project.default_branch || 'main'; + const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${branch}/catalog-info.yaml`; return { entity: { @@ -1312,7 +1384,40 @@ export const expected_location_entities: MockObject[] = 'backstage.io/managed-by-location': `url:${targetUrl}`, 'backstage.io/managed-by-origin-location': `url:${targetUrl}`, }, - name: locationSpecToMetadataName({ target: targetUrl, type: 'url' }), + name: locationSpecToMetadataName({ + target: targetUrl, + type: 'url', + }), + }, + spec: { + presence: 'optional', + target: targetUrl, + type: 'url', + }, + }, + locationKey: 'GitlabDiscoveryEntityProvider:test-id', + }; + }); + +// includes ONLY the projects with the branch declared in the config +export const expected_location_entities_specific_branch: MockObject[] = + all_projects_response.map(project => { + const branch = 'develop'; + const targetUrl = `https://example.com/${project.path_with_namespace}/-/blob/${branch}/catalog-info.yaml`; + + return { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${targetUrl}`, + 'backstage.io/managed-by-origin-location': `url:${targetUrl}`, + }, + name: locationSpecToMetadataName({ + target: targetUrl, + type: 'url', + }), }, spec: { presence: 'optional', diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 7167fc1c2d..0c20732fa7 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -153,7 +153,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_default_branch.filter( entity => !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -187,7 +187,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_default_branch.filter( entity => entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -217,7 +217,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_default_branch.filter( entity => !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -229,8 +229,10 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should filter found projects based on the branch', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + it('should only ingest projects from specific branch', async () => { + const config = new ConfigReader( + mock.config_single_integration_specific_branch, + ); const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), @@ -251,7 +253,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_specific_branch.filter( entity => entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' @@ -263,7 +265,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should only include projects with fallback branch', async () => { + it('should include projects from fallback branch', async () => { const config = new ConfigReader(mock.config_fallbackBranch_branch); const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { @@ -275,21 +277,14 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { schedule, })[0]; - const configured_branch = - mock.config_fallbackBranch_branch.catalog.providers.gitlab['test-id'] - .fallbackBranch; - await provider.connect(entityProviderConnection); await provider.refresh(logger); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter( + entities: mock.expected_location_entities_fallback_branch.filter( entity => - entity.entity.metadata.annotations[ - 'backstage.io/managed-by-location' - ].includes(configured_branch) && !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' ].includes('awesome'), @@ -319,7 +314,7 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ type: 'full', - entities: mock.expected_location_entities.filter(entity => + entities: mock.expected_location_entities_default_branch.filter(entity => entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' ].includes(configured_group), diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index 73ff5f3a01..b2ef475d9d 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GitLabIntegration, ScmIntegrations } from '@backstage/integration'; @@ -34,7 +35,6 @@ import { paginated, readGitlabConfigs, } from '../lib'; -import { LoggerService } from '@backstage/backend-plugin-api'; import * as path from 'path'; @@ -470,14 +470,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { return false; } - const customFallbackBranch = - this.config.fallbackBranch !== 'master' - ? this.config.fallbackBranch - : undefined; - const project_branch = this.config.branch ?? - customFallbackBranch ?? project.default_branch ?? this.config.fallbackBranch; From 67ce37af2cfb312eaa7a151bb5372cbd6b442726 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Tue, 21 May 2024 09:22:12 +0530 Subject: [PATCH 479/567] Updated the note format of documents Signed-off-by: Aditya Kumar --- docs/auth/add-auth-provider.md | 12 +++++--- docs/auth/service-to-service-auth.md | 8 ++++-- docs/integrations/azure/locations.md | 28 +++++++++++-------- docs/integrations/bitbucketCloud/locations.md | 14 ++++++++-- docs/integrations/github/discovery.md | 8 ++++-- docs/integrations/github/org.md | 10 +++++-- docs/integrations/gitlab/discovery.md | 6 +++- docs/integrations/gitlab/org.md | 6 +++- docs/integrations/ldap/org.md | 8 ++++-- docs/permissions/getting-started.md | 6 +++- ...04-authorizing-access-to-paginated-data.md | 6 +++- .../05-frontend-authorization.md | 6 +++- 12 files changed, 86 insertions(+), 32 deletions(-) diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index a43e2307e6..d1dad0b183 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -4,10 +4,14 @@ title: Contributing New Providers description: Documentation on adding new authentication providers --- -> NOTE: The primary audience for this documentation are contributors to the main -> Backstage project that want to add support for new authentication providers. -> While you can follow it to implement your own custom providers it is much -> more advanced than using our built-in providers. +:::note Note + +The primary audience for this documentation are contributors to the main +Backstage project that want to add support for new authentication providers. +While you can follow it to implement your own custom providers it is much +more advanced than using our built-in providers. + +::: ## How Does Authentication Work? diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index c08411aca5..4213f8c0f9 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -156,8 +156,12 @@ payload: - `sub`: the exact string "backstage-server" - `exp`: one hour from the time it was generated, in epoch seconds -> NOTE: The JWT must encode the `alg` header as a protected header, such as with -> [setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader). +:::note Note + +The JWT must encode the `alg` header as a protected header, such as with +[setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader). + +::: The caller then passes along the JWT token with requests in the `Authorization` header: diff --git a/docs/integrations/azure/locations.md b/docs/integrations/azure/locations.md index b59c4149e0..ddc95b65eb 100644 --- a/docs/integrations/azure/locations.md +++ b/docs/integrations/azure/locations.md @@ -68,11 +68,15 @@ integrations: If you do not specify the `organizations` field the credential will be used for all organizations for which no other credential is configured. -> Note: An Azure DevOps provider is added automatically at startup for -> convenience, so you only need to list it if you want to supply a -> [personalAccessToken](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate), -> a [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity), -> or a [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity) +:::note Note + +An Azure DevOps provider is added automatically at startup for +convenience, so you only need to list it if you want to supply a +[personalAccessToken](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate), +a [service principal](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity), +or a [managed identity](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity) + +::: The configuration is a structure with these elements: @@ -86,9 +90,11 @@ The `credentials` element is a structure with these elements: - `tenantId`: The tenant ID of the service principal (required for service principal) - `personalAccessToken`: The personal access token (required for personal access token) -> Note: -> -> - You cannot use a service principal or managed identity for Azure DevOps Server (on-premises) organizations -> - You can only use a service principal or managed identity for Microsoft Entra ID (formerly Azure Active Directory) backed Azure DevOps organizations -> - You can only specify one credential per host without any organizations specified -> - The personal access token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API +:::note Note + +- You cannot use a service principal or managed identity for Azure DevOps Server (on-premises) organizations +- You can only use a service principal or managed identity for Microsoft Entra ID (formerly Azure Active Directory) backed Azure DevOps organizations +- You can only specify one credential per host without any organizations specified +- The personal access token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API + +::: diff --git a/docs/integrations/bitbucketCloud/locations.md b/docs/integrations/bitbucketCloud/locations.md index e7666efb54..9b08a79c63 100644 --- a/docs/integrations/bitbucketCloud/locations.md +++ b/docs/integrations/bitbucketCloud/locations.md @@ -22,10 +22,18 @@ integrations: appPassword: ${BITBUCKET_CLOUD_PASSWORD} ``` -> Note: A public Bitbucket Cloud provider is added automatically at startup for -> convenience, so you only need to list it if you want to supply credentials. +:::note Note -> Note: The credential used for this is type [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work +A public Bitbucket Cloud provider is added automatically at startup for +convenience, so you only need to list it if you want to supply credentials. + +::: + +:::note Note + +The credential used for this is type [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/). An Atlassian Account API key will not work. + +::: Directly under the `bitbucketCloud` key is a list of provider configurations, where you can list the Bitbucket Cloud providers you want to fetch data from. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index f16d1c17b2..920cea9297 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -130,8 +130,12 @@ catalog: This provider supports multiple organizations via unique provider IDs. -> **Note:** It is possible but certainly not recommended to skip the provider ID level. -> If you do so, `default` will be used as provider ID. +:::note Note + +It is possible but certainly not recommended to skip the provider ID level. +If you do so, `default` will be used as provider ID. + +::: - **`catalogPath`** _(optional)_: Default: `/catalog-info.yaml`. diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 06b4e07daa..f94ffe6c7a 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -17,9 +17,13 @@ is a hierarchy of [`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind entities that mirror your org setup. -> Note: This adds `User` and `Group` entities to the catalog, but does not -> provide authentication. See the -> [GitHub auth provider](../../auth/github/provider.md) for that. +:::note Note + +This adds `User` and `Group` entities to the catalog, but does not +provide authentication. See the +[GitHub auth provider](../../auth/github/provider.md) for that. + +::: ## Permissions diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 91efe05fee..b16f6db839 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -136,7 +136,11 @@ To use the discovery provider, you'll need a GitLab integration [set up](locations.md) with a `token`. Then you can add a provider config per group to the catalog configuration. -> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +:::note Note + +If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. + +::: ```yaml title="app-config.yaml" catalog: diff --git a/docs/integrations/gitlab/org.md b/docs/integrations/gitlab/org.md index 14587461e5..2cc0dcfb82 100644 --- a/docs/integrations/gitlab/org.md +++ b/docs/integrations/gitlab/org.md @@ -158,7 +158,11 @@ amount of data, this can take significant time and resources. The token used must have the `read_api` scope, and the Users and Groups fetched will be those visible to the account which provisioned the token. -> > NOTE: if you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. +:::note Note + +If you are using the New Backend System, the `schedule` has to be setup in the config, as shown below. + +::: ```yaml catalog: diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index 49a2776fb1..a10bc3918c 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -29,8 +29,12 @@ to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap ``` -> Note: When configuring to use a Provider instead of a Processor you do not -> need to add a _location_ pointing to your LDAP server +:::note Note + +When configuring to use a Provider instead of a Processor you do not +need to add a _location_ pointing to your LDAP server + +::: Update the catalog plugin initialization in your backend to add the provider and schedule it: diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index e241f0e38e..4dabf3fcbd 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -8,7 +8,11 @@ If you prefer to watch a video instead, you can start with this video introducti -> Note: This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. +:::note Note + +This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. + +::: Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index a9e62040c5..84e86140fc 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -36,7 +36,11 @@ This approach will work for simple cases, but it has a downside: it forces us to To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. -> Note: in order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. +:::note Note + +In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. + +::: ## Creating the read permission diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index 92d855698f..60458aaf4d 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -8,7 +8,11 @@ In the previous sections, we learned how to protect our plugin's backend API rou Take, for example, the "Add" button in our todo list application. When a user clicks this button, the frontend makes a `POST` request to the `/todos` route of our backend. If a user tries to add a todo but is not authorized, they will have no way of knowing this until they perform the action and are faced with an error. This is a poor user experience. We can do better by disabling the add button. -> Note: Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component. +:::note Note + +Placing frontend components behind authorization cannot take the place of placing your backend routes behind authorization. Authorization checks on the frontend should be used in _addition_ to the corresponding backend authorization, as an improvement to the user experience. If you do not place your backend route behind authorization, a malicious actor can still send a request to the route even if you disabled the corresponding frontend component. + +::: ## Using `usePermission` From 3bd04bb3ac6df693de399ffa4dd3fc806c37dcef Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 20 May 2024 13:31:47 +0200 Subject: [PATCH 480/567] reafactor: deprecate legacy handlers and context Signed-off-by: Camila Belo --- .changeset/spicy-camels-happen.md | 5 +++++ packages/backend-common/api-report-alpha.md | 4 ++-- packages/backend-common/api-report.md | 6 +++--- packages/backend-common/src/context/Contexts.ts | 1 + packages/backend-common/src/context/types.ts | 1 + packages/backend-common/src/discovery/HostDiscovery.ts | 1 + packages/backend-common/src/middleware/errorHandler.ts | 1 + .../backend-common/src/middleware/requestLoggingHandler.ts | 1 + .../src/integration/createPermissionIntegrationRouter.ts | 1 + 9 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 .changeset/spicy-camels-happen.md diff --git a/.changeset/spicy-camels-happen.md b/.changeset/spicy-camels-happen.md new file mode 100644 index 0000000000..1017f042ad --- /dev/null +++ b/.changeset/spicy-camels-happen.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +We are deprecating the legacy router handlers and contexts in preparation for the new backend system stable release. diff --git a/packages/backend-common/api-report-alpha.md b/packages/backend-common/api-report-alpha.md index 9d60b68b6b..046e8264fb 100644 --- a/packages/backend-common/api-report-alpha.md +++ b/packages/backend-common/api-report-alpha.md @@ -5,14 +5,14 @@ ```ts import { Duration } from 'luxon'; -// @alpha +// @alpha @deprecated export interface Context { readonly abortSignal: AbortSignal; readonly deadline: Date | undefined; value(key: string): T | undefined; } -// @alpha +// @alpha @deprecated export class Contexts { static root(): Context; static withAbort( diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 42df32dc1d..db798c7bb3 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -309,7 +309,7 @@ export function dropDatabase( ...databaseNames: string[] ): Promise; -// @public +// @public @deprecated export function errorHandler( options?: ErrorHandlerOptions, ): ErrorRequestHandler; @@ -532,7 +532,7 @@ export class HarnessUrlReader implements UrlReader { toString(): string; } -// @public +// @public @deprecated export const HostDiscovery: typeof HostDiscovery_2; // @public @deprecated (undocumented) @@ -733,7 +733,7 @@ export function redactWinstonLogLine( info: winston.Logform.TransformableInfo, ): winston.Logform.TransformableInfo; -// @public +// @public @deprecated export function requestLoggingHandler(logger?: LoggerService): RequestHandler; // @public diff --git a/packages/backend-common/src/context/Contexts.ts b/packages/backend-common/src/context/Contexts.ts index 4eeb56b029..026e3ce4e0 100644 --- a/packages/backend-common/src/context/Contexts.ts +++ b/packages/backend-common/src/context/Contexts.ts @@ -24,6 +24,7 @@ import { ValueContext } from './ValueContext'; * Common context decorators. * * @alpha + * @deprecated This class is not used in the new Backend system, so it is going to be removed in a near future. */ export class Contexts { /** diff --git a/packages/backend-common/src/context/types.ts b/packages/backend-common/src/context/types.ts index 3b53225163..d980f54e43 100644 --- a/packages/backend-common/src/context/types.ts +++ b/packages/backend-common/src/context/types.ts @@ -19,6 +19,7 @@ * to pass along scoped information and abort signals. * * @alpha + * @deprecated This type is not used in the new Backend system, so it is going to be removed in a near future. */ export interface Context { /** diff --git a/packages/backend-common/src/discovery/HostDiscovery.ts b/packages/backend-common/src/discovery/HostDiscovery.ts index 38ac975747..cf0ddff611 100644 --- a/packages/backend-common/src/discovery/HostDiscovery.ts +++ b/packages/backend-common/src/discovery/HostDiscovery.ts @@ -27,6 +27,7 @@ export type { DiscoveryService as PluginEndpointDiscovery } from '@backstage/bac * resolved to the same host, so there won't be any balancing of internal traffic. * * @public + * @deprecated Please import from `@backstage/backend-defaults/discovery` instead. */ export const HostDiscovery = _HostDiscovery; diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 99ede75ab0..110734b0a2 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -61,6 +61,7 @@ export type ErrorHandlerOptions = { * * @public * @returns An Express error request handler + * @deprecated Use {@link @backstage/backend-app-api#MiddlewareFactory.create.error} instead */ export function errorHandler( options: ErrorHandlerOptions = {}, diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.ts b/packages/backend-common/src/middleware/requestLoggingHandler.ts index a03e3c31f6..81dfa1d345 100644 --- a/packages/backend-common/src/middleware/requestLoggingHandler.ts +++ b/packages/backend-common/src/middleware/requestLoggingHandler.ts @@ -26,6 +26,7 @@ import { ConfigReader } from '@backstage/config'; * @public * @param logger - An optional logger to use. If not specified, the root logger will be used. * @returns An Express request handler + * @deprecated @deprecated Use {@link @backstage/backend-app-api#MiddlewareFactory.create.logging} instead */ export function requestLoggingHandler(logger?: LoggerService): RequestHandler { return MiddlewareFactory.create({ diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index ee2444fe89..4a52c33f18 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -481,6 +481,7 @@ export function createPermissionIntegrationRouter< }, ); + // TODO(belugas): Remove this when dropping support to the legacy backend system because setting the error handler manually is no logger required in the new system. router.use(errorHandler()); return router; From 1afb8d43f421fc4e28b39afffe1ccd6b93221bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 May 2024 09:23:24 +0200 Subject: [PATCH 481/567] Update docs/auth/service-to-service-auth.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/auth/service-to-service-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 0b2aa369ec..246bab4415 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -119,7 +119,7 @@ Passed JWTs must have an `iss` claim which matches one of the specified issuers. Algorithms specifies the algorithm(s) that are used to verify the JWT. The passed JWTs must have been signed using one of the listed algorithms. -Audiences speficies the intended audience(s) of the JWT. The passed JWTs must have an "aud" +Audiences specify the intended audience(s) of the JWT. The passed JWTs must have an "aud" claim that matches one of the audiences specified, or have no audience specified. For additional details regarding the JWKS configuration, please consult your authentication From 31864cc9b4dd8c1c2db682afd5d7a1e06901e8b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 May 2024 09:51:12 +0200 Subject: [PATCH 482/567] Update docs/features/software-catalog/extending-the-model.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/features/software-catalog/extending-the-model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index 4625e81b13..51acd44c1d 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -590,7 +590,7 @@ export class FoobarEntitiesProcessor implements CatalogProcessor { #### New Backend -To them use your custom processor, you'll need to add the module to your backend as well as integrate your module with the catalog plugin. +To use your custom processor, you'll need to add the module to your backend as well as integrate your module with the catalog plugin. ```ts title="plugins/catalog-backend-module-foobar/src/index.ts" import { From 22785e3085acaab37c16c3320fb13e336a714892 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Tue, 21 May 2024 09:56:17 +0200 Subject: [PATCH 483/567] Deleting the changeset file as it's not being published Signed-off-by: cmoulliard --- .changeset/few-dodos-cheer.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/few-dodos-cheer.md diff --git a/.changeset/few-dodos-cheer.md b/.changeset/few-dodos-cheer.md deleted file mode 100644 index a80147e112..0000000000 --- a/.changeset/few-dodos-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Register the `catalogPlugin` to the DevApp fixing the issue to launch locally the plugin From 8dd8340258469a6af57d0f93e3e6c4f4332f2944 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Tue, 21 May 2024 10:37:33 +0200 Subject: [PATCH 484/567] Update changeset from major to minor Signed-off-by: Marek Libra --- .changeset/large-months-decide.md | 2 +- .changeset/wild-ears-walk.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/large-months-decide.md b/.changeset/large-months-decide.md index 05d5086794..e9d4c47bbe 100644 --- a/.changeset/large-months-decide.md +++ b/.changeset/large-months-decide.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-notifications-node': major +'@backstage/plugin-notifications-node': minor --- add notifications filtering by processors diff --git a/.changeset/wild-ears-walk.md b/.changeset/wild-ears-walk.md index e6762b82ee..072c545f1a 100644 --- a/.changeset/wild-ears-walk.md +++ b/.changeset/wild-ears-walk.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-notifications-backend': major +'@backstage/plugin-notifications-backend': minor --- adding filtering of notifications by processors From 7d4da32b9191416ddfc0f7d5d0d0e863e14c7ffd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 21 May 2024 11:14:37 +0200 Subject: [PATCH 485/567] microsite/data: update plugin author Signed-off-by: Patrik Oldsberg --- microsite/data/plugins/apollo-explorer.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsite/data/plugins/apollo-explorer.yaml b/microsite/data/plugins/apollo-explorer.yaml index cebf36654e..2b0f834f92 100644 --- a/microsite/data/plugins/apollo-explorer.yaml +++ b/microsite/data/plugins/apollo-explorer.yaml @@ -1,7 +1,7 @@ --- title: Apollo Explorer -author: unredundant -authorUrl: https://github.com/unredundant +author: brizzbuzz +authorUrl: https://github.com/brizzbuzz category: Debugging description: Integrates Apollo Explorer graphs as a tool to browse GraphQL API endpoints inside Backstage. documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/apollo-explorer/plugins/apollo-explorer/README.md From d0048635f75e3b857d774be6018231b0db28d927 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Tue, 21 May 2024 11:53:20 +0200 Subject: [PATCH 486/567] chore: improve tests Signed-off-by: ElaineDeMattosSilvaB --- .../src/__testUtils__/handlers.ts | 6 ++- .../src/__testUtils__/mocks.ts | 7 ++- .../GitlabDiscoveryEntityProvider.test.ts | 51 ++++++++++++++----- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts index f2dcac13cd..80aa0370d3 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/handlers.ts @@ -165,7 +165,11 @@ const httpProjectCatalogDynamic = all_projects_response.map(project => { `${apiBaseUrl}/projects/${path}/repository/files/catalog-info.yaml`, (req, res, ctx) => { const branch = req.url.searchParams.get('ref'); - if (branch === (project.default_branch || 'main' || 'develop')) { + if ( + branch === project.default_branch || + branch === 'main' || + branch === 'develop' + ) { return res(ctx.status(200)); } return res(ctx.status(404, 'Not Found')); diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts index 279da0dfc0..e3154eb1ce 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts @@ -162,7 +162,7 @@ export const config_github_host: MockObject = { }, }; -export const config_single_integration_branch: MockObject = { +export const config_single_integration: MockObject = { integrations: { gitlab: [ { @@ -178,7 +178,6 @@ export const config_single_integration_branch: MockObject = { 'test-id': { host: 'example.com', group: 'group1', - branch: 'main', skipForkedRepos: false, schedule: { frequency: 'PT30M', @@ -724,7 +723,7 @@ export const all_users_response: GitLabUser[] = [ avatar_url: 'https://secure.gravatar.com/', web_url: 'https://gitlab.example/luigi_mario', }, - // malfomed email address + // malformed email address { id: 5, username: 'MarioMario', @@ -1338,7 +1337,7 @@ export const push_modif_event: EventParams = { * Expected Backstage entities */ -// includes only projects that have a default branch (for when the branch and default branch were not set in the config) +// includes only projects that have a default branch (for when the branch and fallback branch were not set in the config) export const expected_location_entities_default_branch: MockObject[] = all_projects_response .filter(project => project.default_branch) diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 0c20732fa7..8c4618efdb 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -61,7 +61,7 @@ describe('GitlabDiscoveryEntityProvider - configuration', () => { }); it('should fail without schedule nor scheduler', () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); expect(() => GitlabDiscoveryEntityProvider.fromConfig(config, { @@ -99,7 +99,7 @@ describe('GitlabDiscoveryEntityProvider - configuration', () => { it('should instantiate provider with single simple discovery config', () => { const schedule = new PersistingTaskRunner(); - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const providers = GitlabDiscoveryEntityProvider.fromConfig(config, { logger, schedule, @@ -229,7 +229,36 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should only ingest projects from specific branch', async () => { + // branch and fallback branch are undefined in the config + it('should ingest catalog from project default branch only', async () => { + const config = new ConfigReader(mock.config_single_integration); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + await provider.connect(entityProviderConnection); + + await provider.refresh(logger); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: mock.expected_location_entities_default_branch.filter( + entity => + !entity.entity.metadata.annotations[ + 'backstage.io/managed-by-location' + ].includes('awesome'), + ), + }); + }); + + // branch was set in the config + it('should ingest catalog from specific branch only', async () => { const config = new ConfigReader( mock.config_single_integration_specific_branch, ); @@ -243,10 +272,6 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { schedule, })[0]; - const configured_branch = - mock.config_single_integration_branch.catalog.providers.gitlab['test-id'] - .branch; - await provider.connect(entityProviderConnection); await provider.refresh(logger); @@ -255,9 +280,6 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { type: 'full', entities: mock.expected_location_entities_specific_branch.filter( entity => - entity.entity.metadata.annotations[ - 'backstage.io/managed-by-location' - ].includes(configured_branch) && !entity.entity.metadata.annotations[ 'backstage.io/managed-by-location' ].includes('awesome'), @@ -265,7 +287,8 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); - it('should include projects from fallback branch', async () => { + // fallback branch was set in the config + it('should ingest catalog from default or fallback branch', async () => { const config = new ConfigReader(mock.config_fallbackBranch_branch); const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { @@ -389,7 +412,7 @@ describe('GitlabDiscoveryEntityProvider - events', () => { expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); }); it('should apply delta mutations on added files from push event', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const schedule = new PersistingTaskRunner(); const events = DefaultEventsService.create({ logger }); @@ -416,7 +439,7 @@ describe('GitlabDiscoveryEntityProvider - events', () => { }); it('should apply delta mutations on removed files from push event', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const schedule = new PersistingTaskRunner(); const events = DefaultEventsService.create({ logger }); const entityProviderConnection: EntityProviderConnection = { @@ -442,7 +465,7 @@ describe('GitlabDiscoveryEntityProvider - events', () => { }); it('should call refresh on added files from push event', async () => { - const config = new ConfigReader(mock.config_single_integration_branch); + const config = new ConfigReader(mock.config_single_integration); const schedule = new PersistingTaskRunner(); const events = DefaultEventsService.create({ logger }); const entityProviderConnection: EntityProviderConnection = { From f27116436b4216f04e51ce2ca21859b4630ca8b9 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Tue, 21 May 2024 12:02:04 +0200 Subject: [PATCH 487/567] chore: add changeset Signed-off-by: ElaineDeMattosSilvaB --- .changeset/perfect-bikes-invite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/perfect-bikes-invite.md diff --git a/.changeset/perfect-bikes-invite.md b/.changeset/perfect-bikes-invite.md new file mode 100644 index 0000000000..82734b451d --- /dev/null +++ b/.changeset/perfect-bikes-invite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Fixed bug in the GitLab discovery where the fallback branch was taking precedence over the GitLab default branch. Relates to issue #24825. From 2418224eb26133e1593c50108fab30f28ad9799b Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 17 May 2024 15:01:07 +0200 Subject: [PATCH 488/567] refactor: apply review suggestion Signed-off-by: Camila Belo --- .changeset/many-moles-sing.md | 2 +- .../08-migrating.md | 41 +++++++++++++++++++ packages/backend-common/api-report.md | 2 +- packages/backend-common/src/config.ts | 1 + .../src/service/createServiceBuilder.ts | 2 +- plugins/app-backend/dev/index.ts | 6 +++ plugins/user-settings-backend/dev/index.ts | 30 +------------- 7 files changed, 53 insertions(+), 31 deletions(-) diff --git a/.changeset/many-moles-sing.md b/.changeset/many-moles-sing.md index e1f5203ed6..8b49f674c7 100644 --- a/.changeset/many-moles-sing.md +++ b/.changeset/many-moles-sing.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Deprecate the legacy `createServiceBuilder`. +We are deprecating the legacy `createServiceBuilder` factory, so if you are still using it, please checkout the migration guide and [migrate](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) your plugin to use the new backend system. diff --git a/docs/backend-system/building-plugins-and-modules/08-migrating.md b/docs/backend-system/building-plugins-and-modules/08-migrating.md index 8a31251ee8..61e244de48 100644 --- a/docs/backend-system/building-plugins-and-modules/08-migrating.md +++ b/docs/backend-system/building-plugins-and-modules/08-migrating.md @@ -214,3 +214,44 @@ The above module can then be installed by the integrator alongside the kubernete backend.add(import('@backstage/plugin-kubernetes-backend')); backend.add(import('@internal/gke-cluster-supplier')); ``` + +### Dev Server + +Follow the steps below to run your migrated plugin on a local development server: + +1. First, delete the `src/run.js` and `src/service/standaloneServer.js` files in case they exist (the `backstage-cli` previously used these files to run legacy backend plugins locally, but they are no longer required). + +2. Next, create a new development backend in the `dev/index.js` file. The dev server is a lite version of a backend app that is mainly used to run your plugin locally, so a simple `kubernetes` backend local development server would look like this: + +```ts title="in dev/index.js" +// This package should be installed as a `dev` dependency +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); +// Path to the file where the plugin is export as default +backend.add(import('../src')); +backend.start(); +``` + +The development server created above will be automatically configured with the default dependency factories, but if you need to mock some of the services your plugin relies on, such as the `rootConfig` service, you can use one of the `mockServices` factories: + +```ts title="in dev/index.js" +//... +// This package should be installed as `devDependecies` +import { mockServices } from '@backstage/backend-test-utils'; + +const backend = createBackend(); +// ... +backend.add( + mockServices.rootConfig.factory({ + data: { + // your config mocked values goes here + }, + }), +); +// ... +``` + +Checkout the [custom service implementations](https://backstage.io/docs/backend-system/building-backends/index#custom-service-implementations) documentation and also the [core service configurations](https://backstage.io/docs/backend-system/core-services/index) page in case you'd like to create your own custom mock factory for one or more services. + +3. Now you can finally start your plugin locally by running `yarn start` from the root folder of your plugin. diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 0f33539ed2..b22b50fc76 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -599,7 +599,7 @@ export type LegacyRootDatabaseService = { forPlugin(pluginId: string): PluginDatabaseManager; }; -// @public +// @public @deprecated export function loadBackendConfig(options: { logger: LoggerService; remote?: LoadConfigOptionsRemote; diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index b452d8db70..1dfa9643e2 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -29,6 +29,7 @@ import { setRootLoggerRedactionList } from './logging/createRootLogger'; * This function should only be called once, during the initialization of the backend. * * @public + * @deprecated Use {@link @backstage/backend-app-api#loadBackendConfig} instead. */ export async function loadBackendConfig(options: { logger: LoggerService; diff --git a/packages/backend-common/src/service/createServiceBuilder.ts b/packages/backend-common/src/service/createServiceBuilder.ts index 5a4f439a32..e731056f72 100644 --- a/packages/backend-common/src/service/createServiceBuilder.ts +++ b/packages/backend-common/src/service/createServiceBuilder.ts @@ -20,7 +20,7 @@ import { ServiceBuilder } from './types'; /** * Creates a new service builder. * @public - * @deprecated We are going to deprecated this old way of creating services in a near future, if you are using this service helper, please checkout the migration guide and make sure you migrate your backend to use the new system: https://backstage.io/docs/backend-system/building-backends/migrating. + * @deprecated We are going to deprecated this old way of creating services in a near future, if you are using this service helper, please checkout the {@link https://backstage.io/docs/backend-system/building-backends/migrating | backend} and {@link https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating | plugin} migration guides. */ export function createServiceBuilder(_module: NodeModule): ServiceBuilder { return new ServiceBuilderImpl(_module); diff --git a/plugins/app-backend/dev/index.ts b/plugins/app-backend/dev/index.ts index 43c75d24df..03f1931337 100644 --- a/plugins/app-backend/dev/index.ts +++ b/plugins/app-backend/dev/index.ts @@ -15,7 +15,13 @@ */ import { createBackend } from '@backstage/backend-defaults'; +import { mockServices } from '@backstage/backend-test-utils'; const backend = createBackend(); +backend.add( + mockServices.rootConfig.factory({ + data: { app: { packageName: 'example-app' } }, + }), +); backend.add(import('../src/alpha')); backend.start(); diff --git a/plugins/user-settings-backend/dev/index.ts b/plugins/user-settings-backend/dev/index.ts index 31963edc95..49fa7cb6bd 100644 --- a/plugins/user-settings-backend/dev/index.ts +++ b/plugins/user-settings-backend/dev/index.ts @@ -15,35 +15,9 @@ */ import { createBackend } from '@backstage/backend-defaults'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { IdentityApi } from '@backstage/plugin-auth-node'; - -const identityMock: IdentityApi = { - async getIdentity({ request }) { - const token = request.headers.authorization?.split(' ')[1]; - return { - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: token || 'user:default/john_doe', - }, - token: token || 'no-token', - }; - }, -}; +import { mockServices } from '@backstage/backend-test-utils'; const backend = createBackend(); -backend.add( - createServiceFactory(() => ({ - service: coreServices.identity, - deps: {}, - async factory() { - return identityMock; - }, - })), -); +backend.add(mockServices.identity.factory()); backend.add(import('../src/alpha')); backend.start(); From 153bbd9c60c3b8b26d678edb6db3d99733cb9913 Mon Sep 17 00:00:00 2001 From: Elaine Mattos Date: Tue, 21 May 2024 13:28:33 +0200 Subject: [PATCH 489/567] Update .changeset/perfect-bikes-invite.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Elaine Mattos --- .changeset/perfect-bikes-invite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/perfect-bikes-invite.md b/.changeset/perfect-bikes-invite.md index 82734b451d..96a4ad5659 100644 --- a/.changeset/perfect-bikes-invite.md +++ b/.changeset/perfect-bikes-invite.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend-module-gitlab': patch --- -Fixed bug in the GitLab discovery where the fallback branch was taking precedence over the GitLab default branch. Relates to issue #24825. +Fixed an issue in `GitlabDiscoveryEntityProvider` where the fallback branch was taking precedence over the GitLab default branch. From a2d26490e618a8a77f176c4853463ecc5a29aed4 Mon Sep 17 00:00:00 2001 From: Bruno Bastos Guimaraes Date: Tue, 21 May 2024 08:38:18 -0300 Subject: [PATCH 490/567] plugins: export catalogTranslationRef Signed-off-by: Bruno Bastos Guimaraes --- .changeset/tall-lies-fetch.md | 5 +++++ plugins/catalog/api-report-alpha.md | 10 ++++++++++ plugins/catalog/src/alpha.ts | 1 + 3 files changed, 16 insertions(+) create mode 100644 .changeset/tall-lies-fetch.md diff --git a/.changeset/tall-lies-fetch.md b/.changeset/tall-lies-fetch.md new file mode 100644 index 0000000000..391aa95aa5 --- /dev/null +++ b/.changeset/tall-lies-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Variable 'catalogTranslationRef' is exported in translation.ts, but it was forgotten to also add it to the alpha entrypoint, so the code never became "visible" diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 3226d10270..75d7e53873 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -11,6 +11,16 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; + +// @alpha (undocumented) +export const catalogTranslationRef: TranslationRef< + 'catalog', + { + readonly 'indexPage.title': '{{orgName}} Catalog'; + readonly 'indexPage.createButtonTitle': 'Create'; + } +>; // @alpha (undocumented) export function createCatalogFilterExtension< diff --git a/plugins/catalog/src/alpha.ts b/plugins/catalog/src/alpha.ts index e80f131817..927d5362b4 100644 --- a/plugins/catalog/src/alpha.ts +++ b/plugins/catalog/src/alpha.ts @@ -16,3 +16,4 @@ export * from './alpha/index'; export { default } from './alpha/index'; +export { catalogTranslationRef } from './translation'; From 4fe62026a75241c269a136e7edacef67488d66f3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 21 May 2024 13:46:11 +0200 Subject: [PATCH 491/567] Update docs/backend-system/building-plugins-and-modules/08-migrating.md Signed-off-by: Patrik Oldsberg --- .../building-plugins-and-modules/08-migrating.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/backend-system/building-plugins-and-modules/08-migrating.md b/docs/backend-system/building-plugins-and-modules/08-migrating.md index 61e244de48..ae63b887ec 100644 --- a/docs/backend-system/building-plugins-and-modules/08-migrating.md +++ b/docs/backend-system/building-plugins-and-modules/08-migrating.md @@ -219,9 +219,9 @@ backend.add(import('@internal/gke-cluster-supplier')); Follow the steps below to run your migrated plugin on a local development server: -1. First, delete the `src/run.js` and `src/service/standaloneServer.js` files in case they exist (the `backstage-cli` previously used these files to run legacy backend plugins locally, but they are no longer required). +1. First, delete the `src/run.ts` and `src/service/standaloneServer.ts` files in case they exist (the `backstage-cli` previously used these files to run legacy backend plugins locally, but they are no longer required). -2. Next, create a new development backend in the `dev/index.js` file. The dev server is a lite version of a backend app that is mainly used to run your plugin locally, so a simple `kubernetes` backend local development server would look like this: +2. Next, create a new development backend in the `dev/index.ts` file. The dev server is a lite version of a backend app that is mainly used to run your plugin locally, so a simple `kubernetes` backend local development server would look like this: ```ts title="in dev/index.js" // This package should be installed as a `dev` dependency From 6a257f0de49e948d7d2a1cb6da40083158ac34b3 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 21 May 2024 14:14:29 +0200 Subject: [PATCH 492/567] chore: pass through an events service optionally Signed-off-by: blam --- .../src/database/DefaultProcessingDatabase.ts | 8 ++++++-- .../src/processing/DefaultCatalogProcessingEngine.ts | 6 +++--- plugins/catalog-backend/src/service/CatalogBuilder.ts | 6 +++--- plugins/catalog-backend/src/service/CatalogPlugin.ts | 10 ++++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index 3eb6b5e8ce..56d143e374 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -42,7 +42,11 @@ import { checkLocationKeyConflict } from './operations/refreshState/checkLocatio import { insertUnprocessedEntity } from './operations/refreshState/insertUnprocessedEntity'; import { updateUnprocessedEntity } from './operations/refreshState/updateUnprocessedEntity'; import { generateStableHash } from './util'; -import { EventBroker, EventParams } from '@backstage/plugin-events-node'; +import { + EventBroker, + EventParams, + EventsService, +} from '@backstage/plugin-events-node'; import { DateTime } from 'luxon'; import { CATALOG_CONFLICTS_TOPIC } from '../constants'; import { CatalogConflictEventPayload } from '../catalog/types'; @@ -60,7 +64,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { database: Knex; logger: LoggerService; refreshInterval: ProcessingIntervalFunction; - eventBroker?: EventBroker; + eventBroker?: EventBroker | EventsService; }, ) { initDatabaseMetrics(options.database); diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 46130a5123..6020811ab0 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -37,7 +37,7 @@ import { withActiveSpan, } from '../util/opentelemetry'; import { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities'; -import { EventBroker } from '@backstage/plugin-events-node'; +import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { CATALOG_ERRORS_TOPIC } from '../constants'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -69,7 +69,7 @@ export class DefaultCatalogProcessingEngine { errors: Error[]; }) => Promise | void; private readonly tracker: ProgressTracker; - private readonly eventBroker?: EventBroker; + private readonly eventBroker?: EventBroker | EventsService; private stopFunc?: () => void; @@ -89,7 +89,7 @@ export class DefaultCatalogProcessingEngine { errors: Error[]; }) => Promise | void; tracker?: ProgressTracker; - eventBroker?: EventBroker; + eventBroker?: EventBroker | EventsService; }) { this.config = options.config; this.scheduler = options.scheduler; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index f305bf2d8b..aabef51195 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -104,7 +104,7 @@ import { import { AuthorizedLocationService } from './AuthorizedLocationService'; import { DefaultProviderDatabase } from '../database/DefaultProviderDatabase'; import { DefaultCatalogDatabase } from '../database/DefaultCatalogDatabase'; -import { EventBroker } from '@backstage/plugin-events-node'; +import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { durationToMilliseconds } from '@backstage/types'; import { AuthService, @@ -182,7 +182,7 @@ export class CatalogBuilder { private readonly permissionRules: CatalogPermissionRuleInput[]; private allowedLocationType: string[]; private legacySingleProcessorValidation = false; - private eventBroker?: EventBroker; + private eventBroker?: EventBroker | EventsService; /** * Creates a catalog builder. @@ -453,7 +453,7 @@ export class CatalogBuilder { /** * Enables the publishing of events for conflicts in the DefaultProcessingDatabase */ - setEventBroker(broker: EventBroker): CatalogBuilder { + setEventBroker(broker: EventBroker | EventsService): CatalogBuilder { this.eventBroker = broker; return this; } diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index ac869cdc2b..cb811dce9b 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -17,6 +17,10 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; +import { + DefaultEventsService, + eventsServiceRef, +} from '@backstage/plugin-events-node'; import { Entity, Validators } from '@backstage/catalog-model'; import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder'; import { @@ -39,6 +43,7 @@ import { import { merge } from 'lodash'; import { Permission } from '@backstage/plugin-permission-common'; import { ForwardedError } from '@backstage/errors'; +import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha'; class CatalogProcessingExtensionPointImpl implements CatalogProcessingExtensionPoint @@ -211,6 +216,7 @@ export const catalogPlugin = createBackendPlugin({ discovery: coreServices.discovery, auth: coreServices.auth, httpAuth: coreServices.httpAuth, + events: eventsServiceRef, }, async init({ logger, @@ -224,6 +230,7 @@ export const catalogPlugin = createBackendPlugin({ discovery, auth, httpAuth, + events, }) { const builder = await CatalogBuilder.create({ config, @@ -236,6 +243,9 @@ export const catalogPlugin = createBackendPlugin({ auth, httpAuth, }); + + builder.setEventBroker(events); + if (processingExtensions.onProcessingErrorHandler) { builder.subscribe({ onProcessingError: processingExtensions.onProcessingErrorHandler, From c7528b09faf09d0f64b25f42c8d5a494c8eb14e5 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 21 May 2024 14:16:35 +0200 Subject: [PATCH 493/567] chore: changeset Signed-off-by: blam --- .changeset/mean-laws-lay.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/mean-laws-lay.md diff --git a/.changeset/mean-laws-lay.md b/.changeset/mean-laws-lay.md new file mode 100644 index 0000000000..a7c629a9a5 --- /dev/null +++ b/.changeset/mean-laws-lay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Pass through `EventsService` too in the new backend system From 32a49366b6287101da5f795ba8583a81e349c0ea Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 21 May 2024 14:44:40 +0200 Subject: [PATCH 494/567] chore: fix typescript Signed-off-by: blam --- plugins/catalog-backend/src/service/CatalogPlugin.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index cb811dce9b..aebd969701 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -17,10 +17,7 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { - DefaultEventsService, - eventsServiceRef, -} from '@backstage/plugin-events-node'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; import { Entity, Validators } from '@backstage/catalog-model'; import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder'; import { @@ -43,7 +40,6 @@ import { import { merge } from 'lodash'; import { Permission } from '@backstage/plugin-permission-common'; import { ForwardedError } from '@backstage/errors'; -import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha'; class CatalogProcessingExtensionPointImpl implements CatalogProcessingExtensionPoint From a112bb59ea5f54093edd9d99fdd8b2e88398da81 Mon Sep 17 00:00:00 2001 From: Frank Kong <50030060+Zaperex@users.noreply.github.com> Date: Tue, 21 May 2024 08:50:05 -0400 Subject: [PATCH 495/567] Update plugins/scaffolder-backend/src/util/checkPermissions.ts Co-authored-by: Vincenzo Scamporlino Signed-off-by: Frank Kong <50030060+Zaperex@users.noreply.github.com> --- plugins/scaffolder-backend/src/util/checkPermissions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/util/checkPermissions.ts b/plugins/scaffolder-backend/src/util/checkPermissions.ts index 40aa673b3f..b6c0395fbb 100644 --- a/plugins/scaffolder-backend/src/util/checkPermissions.ts +++ b/plugins/scaffolder-backend/src/util/checkPermissions.ts @@ -36,8 +36,8 @@ export type checkPermissionOptions = { export async function checkPermission(options: checkPermissionOptions) { const { permissions, permissionService, credentials } = options; if (permissionService) { - const permissionRequest = permissions.map(resourcePermission => ({ - permission: resourcePermission, + const permissionRequest = permissions.map(permission => ({ + permission, })); const authorizationResponses = await permissionService.authorize( permissionRequest, From be05a87507325966aeceb54db8a3a6ada032fbce Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 21 May 2024 15:02:21 +0200 Subject: [PATCH 496/567] chore: enter pre Signed-off-by: blam --- .changeset/pre.json | 187 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 .changeset/pre.json diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000000..e7e26ed1b3 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,187 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "example-app": "0.2.97", + "@backstage/app-defaults": "1.5.5", + "example-app-next": "0.0.11", + "app-next-example-plugin": "0.0.11", + "example-backend": "0.0.26", + "@backstage/backend-app-api": "0.7.3", + "@backstage/backend-common": "0.22.0", + "@backstage/backend-defaults": "0.2.18", + "@backstage/backend-dev-utils": "0.1.4", + "@backstage/backend-dynamic-feature-service": "0.2.10", + "example-backend-legacy": "0.2.98", + "@backstage/backend-openapi-utils": "0.1.11", + "@backstage/backend-plugin-api": "0.6.18", + "@backstage/backend-tasks": "0.5.23", + "@backstage/backend-test-utils": "0.3.8", + "@backstage/catalog-client": "1.6.5", + "@backstage/catalog-model": "1.5.0", + "@backstage/cli": "0.26.5", + "@backstage/cli-common": "0.1.13", + "@backstage/cli-node": "0.2.5", + "@backstage/codemods": "0.1.48", + "@backstage/config": "1.2.0", + "@backstage/config-loader": "1.8.0", + "@backstage/core-app-api": "1.12.5", + "@backstage/core-compat-api": "0.2.5", + "@backstage/core-components": "0.14.7", + "@backstage/core-plugin-api": "1.9.2", + "@backstage/create-app": "0.5.15", + "@backstage/dev-utils": "1.0.32", + "e2e-test": "0.2.16", + "@backstage/e2e-test-utils": "0.1.1", + "@backstage/errors": "1.2.4", + "@backstage/eslint-plugin": "0.1.8", + "@backstage/frontend-app-api": "0.7.0", + "@backstage/frontend-plugin-api": "0.6.5", + "@backstage/frontend-test-utils": "0.1.7", + "@backstage/integration": "1.11.0", + "@backstage/integration-aws-node": "0.1.12", + "@backstage/integration-react": "1.1.27", + "@backstage/release-manifests": "0.0.11", + "@backstage/repo-tools": "0.9.0", + "@techdocs/cli": "1.8.11", + "techdocs-cli-embedded-app": "0.2.96", + "@backstage/test-utils": "1.5.5", + "@backstage/theme": "0.5.4", + "@backstage/types": "1.1.1", + "@backstage/version-bridge": "1.0.8", + "@backstage/plugin-api-docs": "0.11.5", + "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.6", + "@backstage/plugin-app-backend": "0.3.66", + "@backstage/plugin-app-node": "0.1.18", + "@backstage/plugin-app-visualizer": "0.1.6", + "@backstage/plugin-auth-backend": "0.22.5", + "@backstage/plugin-auth-backend-module-atlassian-provider": "0.1.10", + "@backstage/plugin-auth-backend-module-aws-alb-provider": "0.1.10", + "@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.1.1", + "@backstage/plugin-auth-backend-module-bitbucket-provider": "0.1.1", + "@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.1.1", + "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.2.13", + "@backstage/plugin-auth-backend-module-github-provider": "0.1.15", + "@backstage/plugin-auth-backend-module-gitlab-provider": "0.1.15", + "@backstage/plugin-auth-backend-module-google-provider": "0.1.15", + "@backstage/plugin-auth-backend-module-guest-provider": "0.1.4", + "@backstage/plugin-auth-backend-module-microsoft-provider": "0.1.13", + "@backstage/plugin-auth-backend-module-oauth2-provider": "0.1.15", + "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.1.11", + "@backstage/plugin-auth-backend-module-oidc-provider": "0.1.9", + "@backstage/plugin-auth-backend-module-okta-provider": "0.0.11", + "@backstage/plugin-auth-backend-module-pinniped-provider": "0.1.12", + "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.1.10", + "@backstage/plugin-auth-node": "0.4.13", + "@backstage/plugin-auth-react": "0.1.2", + "@backstage/plugin-bitbucket-cloud-common": "0.2.19", + "@backstage/plugin-catalog": "1.20.0", + "@backstage/plugin-catalog-backend": "1.22.0", + "@backstage/plugin-catalog-backend-module-aws": "0.3.13", + "@backstage/plugin-catalog-backend-module-azure": "0.1.38", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.2.1", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.2.5", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.32", + "@backstage/plugin-catalog-backend-module-gcp": "0.1.19", + "@backstage/plugin-catalog-backend-module-gerrit": "0.1.35", + "@backstage/plugin-catalog-backend-module-github": "0.6.1", + "@backstage/plugin-catalog-backend-module-github-org": "0.1.13", + "@backstage/plugin-catalog-backend-module-gitlab": "0.3.16", + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.1", + "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.4.23", + "@backstage/plugin-catalog-backend-module-ldap": "0.5.34", + "@backstage/plugin-catalog-backend-module-msgraph": "0.5.26", + "@backstage/plugin-catalog-backend-module-openapi": "0.1.36", + "@backstage/plugin-catalog-backend-module-puppetdb": "0.1.24", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.16", + "@backstage/plugin-catalog-backend-module-unprocessed": "0.4.5", + "@backstage/plugin-catalog-common": "1.0.23", + "@backstage/plugin-catalog-graph": "0.4.5", + "@backstage/plugin-catalog-import": "0.11.0", + "@backstage/plugin-catalog-node": "1.12.0", + "@backstage/plugin-catalog-react": "1.12.0", + "@backstage/plugin-catalog-unprocessed-entities": "0.2.4", + "@backstage/plugin-catalog-unprocessed-entities-common": "0.0.1", + "@backstage/plugin-config-schema": "0.1.55", + "@backstage/plugin-devtools": "0.1.14", + "@backstage/plugin-devtools-backend": "0.3.4", + "@backstage/plugin-devtools-common": "0.1.9", + "@backstage/plugin-events-backend": "0.3.5", + "@backstage/plugin-events-backend-module-aws-sqs": "0.3.4", + "@backstage/plugin-events-backend-module-azure": "0.2.4", + "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.4", + "@backstage/plugin-events-backend-module-gerrit": "0.2.4", + "@backstage/plugin-events-backend-module-github": "0.2.4", + "@backstage/plugin-events-backend-module-gitlab": "0.2.4", + "@backstage/plugin-events-backend-test-utils": "0.1.28", + "@backstage/plugin-events-node": "0.3.4", + "@internal/plugin-todo-list": "1.0.27", + "@internal/plugin-todo-list-backend": "1.0.27", + "@internal/plugin-todo-list-common": "1.0.18", + "@backstage/plugin-home": "0.7.4", + "@backstage/plugin-home-react": "0.1.13", + "@backstage/plugin-kubernetes": "0.11.10", + "@backstage/plugin-kubernetes-backend": "0.17.1", + "@backstage/plugin-kubernetes-cluster": "0.0.11", + "@backstage/plugin-kubernetes-common": "0.7.6", + "@backstage/plugin-kubernetes-node": "0.1.12", + "@backstage/plugin-kubernetes-react": "0.3.5", + "@backstage/plugin-notifications": "0.2.1", + "@backstage/plugin-notifications-backend": "0.2.1", + "@backstage/plugin-notifications-backend-module-email": "0.0.1", + "@backstage/plugin-notifications-common": "0.0.3", + "@backstage/plugin-notifications-node": "0.1.4", + "@backstage/plugin-org": "0.6.25", + "@backstage/plugin-org-react": "0.1.24", + "@backstage/plugin-permission-backend": "0.5.42", + "@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.15", + "@backstage/plugin-permission-common": "0.7.13", + "@backstage/plugin-permission-node": "0.7.29", + "@backstage/plugin-permission-react": "0.4.22", + "@backstage/plugin-proxy-backend": "0.4.16", + "@backstage/plugin-scaffolder": "1.20.0", + "@backstage/plugin-scaffolder-backend": "1.22.6", + "@backstage/plugin-scaffolder-backend-module-azure": "0.1.10", + "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.2.8", + "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.1.8", + "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.1.8", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.19", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.42", + "@backstage/plugin-scaffolder-backend-module-gerrit": "0.1.10", + "@backstage/plugin-scaffolder-backend-module-gitea": "0.1.8", + "@backstage/plugin-scaffolder-backend-module-github": "0.2.8", + "@backstage/plugin-scaffolder-backend-module-gitlab": "0.4.0", + "@backstage/plugin-scaffolder-backend-module-notifications": "0.0.1", + "@backstage/plugin-scaffolder-backend-module-rails": "0.4.35", + "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.26", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.3.1", + "@backstage/plugin-scaffolder-common": "1.5.2", + "@backstage/plugin-scaffolder-node": "0.4.4", + "@backstage/plugin-scaffolder-node-test-utils": "0.1.4", + "@backstage/plugin-scaffolder-react": "1.8.5", + "@backstage/plugin-search": "1.4.11", + "@backstage/plugin-search-backend": "1.5.8", + "@backstage/plugin-search-backend-module-catalog": "0.1.24", + "@backstage/plugin-search-backend-module-elasticsearch": "1.4.1", + "@backstage/plugin-search-backend-module-explore": "0.1.24", + "@backstage/plugin-search-backend-module-pg": "0.5.27", + "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.1.11", + "@backstage/plugin-search-backend-module-techdocs": "0.1.23", + "@backstage/plugin-search-backend-node": "1.2.22", + "@backstage/plugin-search-common": "1.2.11", + "@backstage/plugin-search-react": "1.7.11", + "@backstage/plugin-signals": "0.0.6", + "@backstage/plugin-signals-backend": "0.1.4", + "@backstage/plugin-signals-node": "0.1.4", + "@backstage/plugin-signals-react": "0.0.3", + "@backstage/plugin-techdocs": "1.10.5", + "@backstage/plugin-techdocs-addons-test-utils": "1.0.32", + "@backstage/plugin-techdocs-backend": "1.10.5", + "@backstage/plugin-techdocs-module-addons-contrib": "1.1.10", + "@backstage/plugin-techdocs-node": "1.12.4", + "@backstage/plugin-techdocs-react": "1.2.4", + "@backstage/plugin-user-settings": "0.8.6", + "@backstage/plugin-user-settings-backend": "0.2.17" + }, + "changesets": [] +} From 9ff15a2b4a82c4e6b92568614a3ff724065b29af Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 21 May 2024 15:20:49 +0200 Subject: [PATCH 497/567] chore: update api -reports Signed-off-by: blam --- plugins/catalog-backend/api-report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 1c65abf83e..f4c10d01a2 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -39,6 +39,7 @@ import { EntityProviderConnection as EntityProviderConnection_2 } from '@backsta import { EntityProviderMutation as EntityProviderMutation_2 } from '@backstage/plugin-catalog-node'; import { EntityRelationSpec as EntityRelationSpec_2 } from '@backstage/plugin-catalog-node'; import { EventBroker } from '@backstage/plugin-events-node'; +import { EventsService } from '@backstage/plugin-events-node'; import { GetEntitiesRequest } from '@backstage/catalog-client'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LocationAnalyzer as LocationAnalyzer_2 } from '@backstage/plugin-catalog-node'; @@ -166,7 +167,7 @@ export class CatalogBuilder { replaceProcessors(processors: CatalogProcessor_2[]): CatalogBuilder; setAllowedLocationTypes(allowedLocationTypes: string[]): CatalogBuilder; setEntityDataParser(parser: CatalogProcessorParser_2): CatalogBuilder; - setEventBroker(broker: EventBroker): CatalogBuilder; + setEventBroker(broker: EventBroker | EventsService): CatalogBuilder; setFieldFormatValidators(validators: Partial): CatalogBuilder; setLocationAnalyzer(locationAnalyzer: LocationAnalyzer_2): CatalogBuilder; setPlaceholderResolver( From 3d71ade01ef2bafbe42a01739e71779c065f53f6 Mon Sep 17 00:00:00 2001 From: Frank Kong <50030060+Zaperex@users.noreply.github.com> Date: Tue, 21 May 2024 10:14:53 -0400 Subject: [PATCH 498/567] Update docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: Frank Kong <50030060+Zaperex@users.noreply.github.com> --- ...authorizing-scaffolder-tasks-parameters-steps-and-actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md index 9ca4a230cc..27696af1a9 100644 --- a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md @@ -176,7 +176,7 @@ class ExamplePermissionPolicy implements PermissionPolicy { ### Authorizing scaffolder tasks -The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to the scaffolder. +The scaffolder plugin also exposes permissions that can restrict access to tasks, task logs, task creation, and task cancellation. This can be useful if you want to control who has access to these areas of the scaffolder. ```ts title="packages/src/backend/plugins/permissions.ts" /* highlight-add-start */ From cce04957e91f32bb05963237e71660fd53530377 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 21 May 2024 14:41:46 +0000 Subject: [PATCH 499/567] Version Packages (next) --- .changeset/create-app-1716302437.md | 5 + .changeset/pre.json | 39 +- docs/releases/v1.28.0-next.0-changelog.md | 2101 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 11 + packages/app-defaults/package.json | 2 +- packages/app-next-example-plugin/CHANGELOG.md | 8 + packages/app-next-example-plugin/package.json | 2 +- packages/app-next/CHANGELOG.md | 43 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 41 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 25 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 22 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 20 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 26 + .../package.json | 2 +- packages/backend-legacy/CHANGELOG.md | 42 + packages/backend-legacy/package.json | 2 +- packages/backend-openapi-utils/CHANGELOG.md | 8 + packages/backend-openapi-utils/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 14 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 13 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 15 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 37 + packages/backend/package.json | 2 +- packages/cli-node/CHANGELOG.md | 10 + packages/cli-node/package.json | 2 +- packages/cli/CHANGELOG.md | 18 + packages/cli/package.json | 2 +- packages/core-compat-api/CHANGELOG.md | 9 + packages/core-compat-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 11 + packages/core-components/package.json | 2 +- packages/create-app/CHANGELOG.md | 8 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 14 + packages/dev-utils/package.json | 2 +- packages/e2e-test/CHANGELOG.md | 9 + packages/e2e-test/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 15 + packages/frontend-app-api/package.json | 2 +- packages/frontend-plugin-api/CHANGELOG.md | 10 + packages/frontend-plugin-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 10 + packages/frontend-test-utils/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 12 + packages/repo-tools/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 19 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 11 + packages/techdocs-cli/package.json | 2 +- packages/test-utils/CHANGELOG.md | 13 + packages/test-utils/package.json | 2 +- packages/theme/CHANGELOG.md | 6 + packages/theme/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 16 + plugins/api-docs/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 17 + plugins/app-backend/package.json | 2 +- plugins/app-node/CHANGELOG.md | 8 + plugins/app-node/package.json | 2 +- plugins/app-visualizer/CHANGELOG.md | 9 + plugins/app-visualizer/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 30 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 13 + plugins/auth-node/package.json | 2 +- plugins/auth-react/CHANGELOG.md | 9 + plugins/auth-react/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 17 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 13 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 13 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 14 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 28 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 14 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 18 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 14 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 18 + plugins/catalog-react/package.json | 2 +- .../catalog-unprocessed-entities/CHANGELOG.md | 10 + .../catalog-unprocessed-entities/package.json | 2 +- plugins/catalog/CHANGELOG.md | 21 + plugins/catalog/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 10 + plugins/config-schema/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 17 + plugins/devtools-backend/package.json | 2 +- plugins/devtools/CHANGELOG.md | 13 + plugins/devtools/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../events-backend-module-azure/CHANGELOG.md | 8 + .../events-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../events-backend-module-gerrit/CHANGELOG.md | 8 + .../events-backend-module-gerrit/package.json | 2 +- .../events-backend-module-github/CHANGELOG.md | 9 + .../events-backend-module-github/package.json | 2 +- .../events-backend-module-gitlab/CHANGELOG.md | 9 + .../events-backend-module-gitlab/package.json | 2 +- .../events-backend-test-utils/CHANGELOG.md | 7 + .../events-backend-test-utils/package.json | 2 +- plugins/events-backend/CHANGELOG.md | 10 + plugins/events-backend/package.json | 2 +- plugins/events-node/CHANGELOG.md | 8 + plugins/events-node/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 10 + .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 8 + plugins/example-todo-list/package.json | 2 +- plugins/home-react/CHANGELOG.md | 8 + plugins/home-react/package.json | 2 +- plugins/home/CHANGELOG.md | 17 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 20 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 12 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 10 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 12 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 12 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 17 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 12 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 14 + plugins/notifications/package.json | 2 +- plugins/org-react/CHANGELOG.md | 11 + plugins/org-react/package.json | 2 +- plugins/org/CHANGELOG.md | 13 + plugins/org/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 13 + plugins/permission-backend/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 12 + plugins/permission-node/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 56 + plugins/proxy-backend/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 32 + plugins/scaffolder-backend/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 11 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 13 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 16 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 22 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 13 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 15 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 20 + plugins/search-backend/package.json | 2 +- plugins/search-react/CHANGELOG.md | 13 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 16 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 14 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 12 + plugins/signals-node/package.json | 2 +- plugins/signals/CHANGELOG.md | 11 + plugins/signals/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 15 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 18 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 14 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 11 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 25 + plugins/techdocs/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 13 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 15 + plugins/user-settings/package.json | 2 +- yarn.lock | 245 +- 303 files changed, 4575 insertions(+), 213 deletions(-) create mode 100644 .changeset/create-app-1716302437.md create mode 100644 docs/releases/v1.28.0-next.0-changelog.md diff --git a/.changeset/create-app-1716302437.md b/.changeset/create-app-1716302437.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1716302437.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/pre.json b/.changeset/pre.json index e7e26ed1b3..7a0fc7ed6b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -183,5 +183,42 @@ "@backstage/plugin-user-settings": "0.8.6", "@backstage/plugin-user-settings-backend": "0.2.17" }, - "changesets": [] + "changesets": [ + "brave-apples-move", + "breezy-badgers-train", + "calm-cars-serve", + "calm-plums-wink", + "cold-seas-end", + "create-app-1716302437", + "cyan-paws-beg", + "cyan-snails-peel", + "eighty-kings-dress", + "empty-spoons-tell", + "four-adults-mix", + "gentle-baboons-peel", + "itchy-spoons-cry", + "late-ants-impress", + "late-students-live", + "loud-pumpkins-bow", + "lovely-hats-pay", + "lucky-taxis-rule", + "many-moles-sing", + "mean-laws-lay", + "new-numbers-hug", + "nine-ties-type", + "old-trees-check", + "olive-mangos-tickle", + "rude-kings-press", + "seven-geese-raise", + "slimy-fans-raise", + "smooth-gifts-nail", + "sour-colts-juggle", + "spicy-camels-happen", + "tiny-pandas-return", + "wet-crabs-guess", + "wild-doors-cheat", + "wise-vans-sin", + "wise-wasps-look", + "young-camels-return" + ] } diff --git a/docs/releases/v1.28.0-next.0-changelog.md b/docs/releases/v1.28.0-next.0-changelog.md new file mode 100644 index 0000000000..69c17d7c7d --- /dev/null +++ b/docs/releases/v1.28.0-next.0-changelog.md @@ -0,0 +1,2101 @@ +# Release v1.28.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.28.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.28.0-next.0) + +## @backstage/plugin-catalog-backend@1.23.0-next.0 + +### Minor Changes + +- c7528b0: Pass through `EventsService` too in the new backend system + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 1779188: Start using the `isDatabaseConflictError` helper from the `@backstage/backend-plugin-api` package in order to avoid dependency with the soon to deprecate `@backstage/backend-common` package. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/backend-openapi-utils@0.1.12-next.0 + - @backstage/plugin-catalog-node@1.12.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@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-proxy-backend@0.5.0-next.0 + +### Minor Changes + +- 88480e4: **BREAKING**: The proxy backend plugin is now protected by Backstage auth, by + default. Unless specifically configured (see below), all proxy endpoints will + reject requests immediately unless a valid Backstage user or service token is + passed along with the request. This aligns the proxy with how other Backstage + backends behave out of the box, and serves to protect your upstreams from + unauthorized access. + + A proxy configuration section can now look as follows: + + ```yaml + proxy: + endpoints: + '/pagerduty': + target: https://api.pagerduty.com + credentials: require # NEW! + headers: + Authorization: Token token=${PAGERDUTY_TOKEN} + ``` + + There are three possible `credentials` settings at this point: + + - `require`: Callers must provide Backstage user or service credentials with + each request. The credentials are not forwarded to the proxy target. + - `forward`: Callers must provide Backstage user or service credentials with + each request, and those credentials are forwarded to the proxy target. + - `dangerously-allow-unauthenticated`: No Backstage credentials are required to + access this proxy target. The target can still apply its own credentials + checks, but the proxy will not help block non-Backstage-blessed callers. If + you also add `allowedHeaders: ['Authorization']` to an endpoint configuration, + then the Backstage token (if provided) WILL be forwarded. + + The value `dangerously-allow-unauthenticated` was the old default. + + The value `require` is the new default, so requests that were previously + permitted may now start resulting in `401 Unauthorized` responses. If you have + `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`, this does not + apply; the proxy will behave as if all endpoints were set to + `dangerously-allow-unauthenticated`. + + If you have proxy endpoints that require unauthenticated access still, please + add `credentials: dangerously-allow-unauthenticated` to their declarations in + your app-config. + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/app-defaults@1.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/backend-app-api@0.7.6-next.0 + +### Patch Changes + +- b7de623: Fixed a potential crash when passing an object with a `null` prototype as log meta. +- 7d30d95: Fixing issue with log meta fields possibly being circular refs +- 6a576dc: Stop using `getVoidLogger` in tests to reduce the dependency on the soon-to-deprecate `backstage-common` package. +- 6551b3d: Deprecated core service factories and implementations and moved them over to + subpath exports on `@backstage/backend-defaults` instead. E.g. + `@backstage/backend-defaults/scheduler` is where the service factory and default + implementation of `coreServices.scheduler` now lives. +- d617103: Updating the logger redaction message to something less dramatic +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-common@0.22.1-next.0 + +### Patch Changes + +- c6c0919: Updated configuration schema to include the `useRedisSets` cache config option. +- 1779188: In preparation to the new backend system stable release, the `isDatabaseConflictError` helper have been moved to the `@backstage/backend-plugin-api` package and deprecated from `@backstage/backend-common`. +- 8869b8e: We are deprecating the legacy `createServiceBuilder` factory, so if you are still using it, please checkout the migration guide and [migrate](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) your plugin to use the new backend system. +- 3bd04bb: We are deprecating the legacy router handlers and contexts in preparation for the new backend system stable release. +- 6a576dc: Deprecate legacy service logger helpers and stop using `getVoidLogger` in tests. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.19-next.0 + +### Patch Changes + +- 6551b3d: Added core service factories and implementations from + `@backstage/backend-app-api`. They are now available as subpath exports, e.g. + `@backstage/backend-defaults/scheduler` is where the service factory and default + implementation of `coreServices.scheduler` now lives. They have been marked as + deprecated in their old locations. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-dynamic-feature-service@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-events-backend@0.3.6-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-app-node@0.1.19-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/backend-openapi-utils@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.6.19-next.0 + +### Patch Changes + +- 6551b3d: Moved the declaration of the `SchedulerService` here, along with prefixed versions of all of the types it depends on, from `@backstage/backend-tasks` +- 1779188: Start using the `isDatabaseConflictError` helper from the `@backstage/backend-plugin-api` package in order to avoid dependency with the soon to deprecate `@backstage/backend-common` package. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/backend-tasks@0.5.24-next.0 + +### Patch Changes + +- 736bc3c: Marked all exports as deprecated and pointed at `@backstage/backend-plugin-api` and `@backstage/backend-defaults` +- 6a576dc: Deprecate the legacy `TaskScheduler.fromConfig` method and stop using the `getVoidlogger` in tests files to reduce the dependency on the soon-to-deprecate `backstage-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.3.9-next.0 + +### Patch Changes + +- 6a576dc: Fix the logger service mock to prevent returning `undefined` from the `child` method. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/cli@0.26.6-next.0 + +### Patch Changes + +- 009da47: Fix `versions:check --fix` when `yarn.lock` has multiple joint versions in the same section +- 9ee948a: Bump `esbuild` target for package builds to `ES2022`. +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.11.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/cli-node@0.2.6-next.0 + +### Patch Changes + +- 93be042: Upgraded @yarnpkg/parsers to stable 3.0 +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/version-bridge@1.0.8 + +## @backstage/core-components@0.14.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.16-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/frontend-app-api@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-test-utils@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.1-next.0 + - @backstage/test-utils@1.5.6-next.0 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-techdocs-node@1.12.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/theme@0.5.6-next.0 + +### Patch Changes + +- 702fa7d: Internal refactor to fix an issue where the MUI 5 `v5-` class prefixing gets removed by tree shaking. + +## @backstage/plugin-api-docs@0.11.6-next.0 + +### Patch Changes + +- 96cd13e: `DefaultApiExplorerPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/plugin-app-backend@0.3.68-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 82c2b90: Restore the support of external config schema in the router of the `app-backend` plugin, which was broken in release `1.26.0`. + This support is critical for dynamic frontend plugins to have access to their config values. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-app-node@0.1.19-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-app-node@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config-loader@1.8.0 + +## @backstage/plugin-app-visualizer@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + +## @backstage/plugin-auth-backend@0.22.6-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.12-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.12-next.0 + - @backstage/plugin-catalog-node@1.12.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.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-auth-node@0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-react@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog@1.20.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration-react@1.1.27 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-catalog-backend-module-aws@0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-catalog-backend-module-azure@0.1.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/backend-openapi-utils@0.1.12-next.0 + - @backstage/plugin-catalog-node@1.12.1-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.6-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.19 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-catalog-backend-module-github@0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-backend-module-github@0.6.2-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-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.23 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-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.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-scaffolder-common@1.5.2 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.1 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-catalog-graph@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-catalog-import@0.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-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.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-catalog-node@1.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-permission-node@0.7.30-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.23 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-catalog-react@1.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration-react@1.1.27 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + +## @backstage/plugin-config-schema@0.1.56-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/plugin-devtools-backend@0.3.5-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-events-backend@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend-module-azure@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + +## @backstage/plugin-events-backend-module-github@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-gitlab@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-test-utils@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-events-node@0.3.5-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + +## @backstage/plugin-home@0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-home-react@0.1.14-next.0 + +## @backstage/plugin-home-react@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + +## @backstage/plugin-kubernetes@0.11.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.6 + - @backstage/plugin-kubernetes-react@0.3.6-next.0 + +## @backstage/plugin-kubernetes-backend@0.17.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-kubernetes-node@0.1.13-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-catalog-node@1.12.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-kubernetes-common@0.7.6 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-kubernetes-cluster@0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.6 + - @backstage/plugin-kubernetes-react@0.3.6-next.0 + +## @backstage/plugin-kubernetes-node@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-kubernetes-react@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.6 + +## @backstage/plugin-notifications@0.2.2-next.0 + +### Patch Changes + +- 7f02684: Do not always show scrollbars in notification description +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-react@0.0.3 + +## @backstage/plugin-notifications-backend@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-notifications-node@0.1.5-next.0 + - @backstage/plugin-signals-node@0.1.5-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-notifications-common@0.0.3 + +## @backstage/plugin-notifications-backend-module-email@0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-notifications-node@0.1.5-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.3 + +## @backstage/plugin-notifications-node@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-signals-node@0.1.5-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.3 + +## @backstage/plugin-org@0.6.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-org-react@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-permission-backend@0.5.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-permission-node@0.7.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-scaffolder@1.20.1-next.0 + +### Patch Changes + +- 612a453: Change owner to project for azure host +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-scaffolder-common@1.5.2 + +## @backstage/plugin-scaffolder-backend@1.22.8-next.0 + +### Patch Changes + +- 7d30d95: Fixing issue with log meta fields possibly being circular refs +- d617103: Updating the logger redaction message to something less dramatic +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.0 + - @backstage/plugin-catalog-node@1.12.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@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-scaffolder-common@1.5.2 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.0 + +### Patch Changes + +- f145a04: Added handling for dry run to githubPullRequest and githubWebhook and added tests for this functionality +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-notifications-node@0.1.5-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-notifications-common@0.0.3 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.1.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.2 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.5-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-test-utils@0.3.9-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-react@1.8.6-next.0 + +### Patch Changes + +- 86dc29d: Links that are rendered in the markdown in the `ScaffolderField` component are now opened in new tabs. +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.2 + +## @backstage/plugin-search@1.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend@1.5.10-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 5b6f979: Split backend search plugin startup into "init" and "start" stages to ensure necessary initialization has happened before startup +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-defaults@0.2.19-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/repo-tools@0.9.1-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/backend-openapi-utils@0.1.12-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-catalog@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.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.23 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-explore@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-pg@0.5.28-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-techdocs@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-techdocs-node@1.12.5-next.0 + - @backstage/plugin-catalog-node@1.12.1-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.23 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-node@1.2.24-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- 5b6f979: Split backend search plugin startup into "init" and "start" stages to ensure necessary initialization has happened before startup +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-react@1.7.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-signals@0.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.3 + +## @backstage/plugin-signals-backend@0.1.5-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-signals-node@0.1.5-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-signals-node@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-techdocs@1.10.6-next.0 + +### Patch Changes + +- 654af4a: mkdocs-material have updated their CSS variable template, and a few are unset in Backstage. This patch adds the missing variables to ensure coverage. +- 96cd13e: `TechDocsIndexPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. +- e40bd9a: Fixed bug in CopyToClipboardButton component where positioning of the "Copy to clipboard" button in techdocs code snippets was broken in some cases +- 1256d88: Fix weird opening behaviour of the component. +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/test-utils@1.5.6-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-techdocs-backend@1.10.6-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.0 + - @backstage/plugin-techdocs-node@1.12.5-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@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + +## @backstage/plugin-techdocs-node@1.12.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-techdocs-react@1.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-user-settings-backend@0.2.18-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## example-app@0.2.98-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/plugin-notifications@0.2.2-next.0 + - @backstage/plugin-scaffolder-react@1.8.6-next.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/plugin-api-docs@0.11.6-next.0 + - @backstage/cli@0.26.6-next.0 + - @backstage/plugin-scaffolder@1.20.1-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/frontend-app-api@0.7.1-next.0 + - @backstage/plugin-home@0.7.5-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-signals@0.0.7-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/plugin-user-settings@0.8.7-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-graph@0.4.6-next.0 + - @backstage/plugin-catalog-import@0.11.1-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.0 + - @backstage/plugin-devtools@0.1.15-next.0 + - @backstage/plugin-kubernetes@0.11.11-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.0 + - @backstage/plugin-org@0.6.26-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search@1.4.12-next.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.0 + +## example-app-next@0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/plugin-notifications@0.2.2-next.0 + - @backstage/plugin-scaffolder-react@1.8.6-next.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/plugin-api-docs@0.11.6-next.0 + - @backstage/cli@0.26.6-next.0 + - @backstage/plugin-scaffolder@1.20.1-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/frontend-app-api@0.7.1-next.0 + - @backstage/plugin-home@0.7.5-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-signals@0.0.7-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/plugin-user-settings@0.8.7-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-app-visualizer@0.1.7-next.0 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-graph@0.4.6-next.0 + - @backstage/plugin-catalog-import@0.11.1-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.0 + - @backstage/plugin-kubernetes@0.11.11-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.0 + - @backstage/plugin-org@0.6.26-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search@1.4.12-next.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.0 + +## app-next-example-plugin@0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/frontend-plugin-api@0.6.6-next.0 + +## example-backend@0.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.0 + - @backstage/plugin-techdocs-backend@1.10.6-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-search-backend@1.5.10-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/plugin-app-backend@0.3.68-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/plugin-signals-backend@0.1.5-next.0 + - @backstage/backend-defaults@0.2.19-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-backend@1.22.8-next.0 + - @backstage/plugin-kubernetes-backend@0.17.2-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.0 + - @backstage/plugin-notifications-backend@0.2.2-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.16-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-permission-common@0.7.13 + +## example-backend-legacy@0.2.99-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.0 + - @backstage/plugin-techdocs-backend@1.10.6-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-search-backend@1.5.10-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/plugin-app-backend@0.3.68-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.28-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/plugin-signals-backend@0.1.5-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-scaffolder-backend@1.22.8-next.0 + - @backstage/plugin-kubernetes-backend@0.17.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.0 + - example-app@0.2.98-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.0 + - @backstage/plugin-events-backend@0.3.6-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.0 + - @backstage/plugin-signals-node@0.1.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-permission-common@0.7.13 + +## e2e-test@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.16-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.97-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/cli@0.26.6-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/test-utils@1.5.6-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog@1.20.1-next.0 + +## @internal/plugin-todo-list@1.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + +## @internal/plugin-todo-list-backend@1.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 diff --git a/package.json b/package.json index 53c87544af..7b7692623a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.27.0", + "version": "1.28.0-next.0", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 5ab386f512..be98cfedeb 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/app-defaults +## 1.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-permission-react@0.4.22 + ## 1.5.5 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 27533f2b9f..473f7e76ac 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.5", + "version": "1.5.6-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 356ed1f378..a54715b18d 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.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/frontend-plugin-api@0.6.6-next.0 + ## 0.0.11 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index e601961ef6..cba0c558a5 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.11", + "version": "0.0.12-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 3f03cac54f..1bf9501f3b 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,48 @@ # example-app-next +## 0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/plugin-notifications@0.2.2-next.0 + - @backstage/plugin-scaffolder-react@1.8.6-next.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/plugin-api-docs@0.11.6-next.0 + - @backstage/cli@0.26.6-next.0 + - @backstage/plugin-scaffolder@1.20.1-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/frontend-app-api@0.7.1-next.0 + - @backstage/plugin-home@0.7.5-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-signals@0.0.7-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/plugin-user-settings@0.8.7-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-app-visualizer@0.1.7-next.0 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-graph@0.4.6-next.0 + - @backstage/plugin-catalog-import@0.11.1-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.0 + - @backstage/plugin-kubernetes@0.11.11-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.0 + - @backstage/plugin-org@0.6.26-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search@1.4.12-next.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.0 + ## 0.0.11 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index d5f69915a9..1921ebe3cb 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.11", + "version": "0.0.12-next.0", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index fa01c31fd6..9d1606c9f4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,46 @@ # example-app +## 0.2.98-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/plugin-notifications@0.2.2-next.0 + - @backstage/plugin-scaffolder-react@1.8.6-next.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/plugin-api-docs@0.11.6-next.0 + - @backstage/cli@0.26.6-next.0 + - @backstage/plugin-scaffolder@1.20.1-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/frontend-app-api@0.7.1-next.0 + - @backstage/plugin-home@0.7.5-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-signals@0.0.7-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/plugin-user-settings@0.8.7-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-graph@0.4.6-next.0 + - @backstage/plugin-catalog-import@0.11.1-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.5-next.0 + - @backstage/plugin-devtools@0.1.15-next.0 + - @backstage/plugin-kubernetes@0.11.11-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.0 + - @backstage/plugin-org@0.6.26-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search@1.4.12-next.0 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.11-next.0 + ## 0.2.97 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 9978c6fcb4..013a3f7c41 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.97", + "version": "0.2.98-next.0", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 96259c80da..302c14d6f1 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/backend-app-api +## 0.7.6-next.0 + +### Patch Changes + +- b7de623: Fixed a potential crash when passing an object with a `null` prototype as log meta. +- 7d30d95: Fixing issue with log meta fields possibly being circular refs +- 6a576dc: Stop using `getVoidLogger` in tests to reduce the dependency on the soon-to-deprecate `backstage-common` package. +- 6551b3d: Deprecated core service factories and implementations and moved them over to + subpath exports on `@backstage/backend-defaults` instead. E.g. + `@backstage/backend-defaults/scheduler` is where the service factory and default + implementation of `coreServices.scheduler` now lives. +- d617103: Updating the logger redaction message to something less dramatic +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.7.3 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 414a4333e7..988dfd3db9 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.7.3", + "version": "0.7.6-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 70136051d0..5d5920998b 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/backend-common +## 0.22.1-next.0 + +### Patch Changes + +- c6c0919: Updated configuration schema to include the `useRedisSets` cache config option. +- 1779188: In preparation to the new backend system stable release, the `isDatabaseConflictError` helper have been moved to the `@backstage/backend-plugin-api` package and deprecated from `@backstage/backend-common`. +- 8869b8e: We are deprecating the legacy `createServiceBuilder` factory, so if you are still using it, please checkout the migration guide and [migrate](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) your plugin to use the new backend system. +- 3bd04bb: We are deprecating the legacy router handlers and contexts in preparation for the new backend system stable release. +- 6a576dc: Deprecate legacy service logger helpers and stop using `getVoidLogger` in tests. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + ## 0.22.0 ### Minor Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 8e0d388352..564d9589bf 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.22.0", + "version": "0.22.1-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 b43a592ba3..fe8952c64a 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/backend-defaults +## 0.2.19-next.0 + +### Patch Changes + +- 6551b3d: Added core service factories and implementations from + `@backstage/backend-app-api`. They are now available as subpath exports, e.g. + `@backstage/backend-defaults/scheduler` is where the service factory and default + implementation of `coreServices.scheduler` now lives. They have been marked as + deprecated in their old locations. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.18 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 4f502b1598..e4db289e33 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.2.18", + "version": "0.2.19-next.0", "backstage": { "role": "node-library" }, diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index ad620ee060..01b4447eb0 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.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-events-backend@0.3.6-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-app-node@0.1.19-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 0.2.10 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 4c7a7bbaf5..34567a6c20 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.10", + "version": "0.2.11-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 9a5a720491..183b4a4954 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,47 @@ # example-backend-legacy +## 0.2.99-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.0 + - @backstage/plugin-techdocs-backend@1.10.6-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-search-backend@1.5.10-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/plugin-app-backend@0.3.68-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.28-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/plugin-signals-backend@0.1.5-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-scaffolder-backend@1.22.8-next.0 + - @backstage/plugin-kubernetes-backend@0.17.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.0 + - example-app@0.2.98-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.0 + - @backstage/plugin-events-backend@0.3.6-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.0 + - @backstage/plugin-signals-node@0.1.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-permission-common@0.7.13 + ## 0.2.98 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index d5a3065df5..cad1a50b7f 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.98", + "version": "0.2.99-next.0", "backstage": { "role": "backend" }, diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index d3d8e4bdf8..54055c80ac 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.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/errors@1.2.4 + ## 0.1.11 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index badda72b04..60bb7728b0 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.11", + "version": "0.1.12-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 494634a9de..74b0cfdab9 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-plugin-api +## 0.6.19-next.0 + +### Patch Changes + +- 6551b3d: Moved the declaration of the `SchedulerService` here, along with prefixed versions of all of the types it depends on, from `@backstage/backend-tasks` +- 1779188: Start using the `isDatabaseConflictError` helper from the `@backstage/backend-plugin-api` package in order to avoid dependency with the soon to deprecate `@backstage/backend-common` package. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + ## 0.6.18 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 4569f205f9..ec84d31a45 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.6.18", + "version": "0.6.19-next.0", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 629f3e7853..25290d5575 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/backend-tasks +## 0.5.24-next.0 + +### Patch Changes + +- 736bc3c: Marked all exports as deprecated and pointed at `@backstage/backend-plugin-api` and `@backstage/backend-defaults` +- 6a576dc: Deprecate the legacy `TaskScheduler.fromConfig` method and stop using the `getVoidlogger` in tests files to reduce the dependency on the soon-to-deprecate `backstage-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.5.23 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index c0331c5e4c..c57e334185 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.23", + "version": "0.5.24-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 d20d983fab..aaf2cf4e27 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/backend-test-utils +## 0.3.9-next.0 + +### Patch Changes + +- 6a576dc: Fix the logger service mock to prevent returning `undefined` from the `child` method. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.3.8 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index cb7711adfd..00d5b3b4e4 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.3.8", + "version": "0.3.9-next.0", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index a0e705f22f..3b4104ece6 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,42 @@ # example-backend +## 0.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.0 + - @backstage/plugin-techdocs-backend@1.10.6-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-search-backend@1.5.10-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/plugin-app-backend@0.3.68-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/plugin-signals-backend@0.1.5-next.0 + - @backstage/backend-defaults@0.2.19-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-backend@1.22.8-next.0 + - @backstage/plugin-kubernetes-backend@0.17.2-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.0 + - @backstage/plugin-notifications-backend@0.2.2-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.16-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-permission-common@0.7.13 + ## 0.0.26 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index a373a142a0..4ab3798633 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.26", + "version": "0.0.27-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md index dd58feca17..26c9af24ef 100644 --- a/packages/cli-node/CHANGELOG.md +++ b/packages/cli-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/cli-node +## 0.2.6-next.0 + +### Patch Changes + +- 93be042: Upgraded @yarnpkg/parsers to stable 3.0 +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.5 ### Patch Changes diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index a786d71949..05037a10df 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-node", - "version": "0.2.5", + "version": "0.2.6-next.0", "description": "Node.js library for Backstage CLIs", "backstage": { "role": "node-library" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 7f59e4ef73..f3b7d52c7e 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/cli +## 0.26.6-next.0 + +### Patch Changes + +- 009da47: Fix `versions:check --fix` when `yarn.lock` has multiple joint versions in the same section +- 9ee948a: Bump `esbuild` target for package builds to `ES2022`. +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.11.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + ## 0.26.5 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 67492919d0..8479ff192e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.26.5", + "version": "0.26.6-next.0", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 67084d7926..c6869bee6d 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.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/version-bridge@1.0.8 + ## 0.2.5 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 9124060464..cf84e36b5c 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.5", + "version": "0.2.6-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 4c2a973813..8b2e59d15f 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-components +## 0.14.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/version-bridge@1.0.8 + ## 0.14.7 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 435a013062..cdcdbb67a0 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.7", + "version": "0.14.8-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index be4c46ab03..6fa1ef51c8 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/create-app +## 0.5.16-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.13 + ## 0.5.15 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 422442ed2b..d8bfb337fa 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.15", + "version": "0.5.16-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index c8e32a39d0..e8389d7744 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/dev-utils +## 1.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.0.32 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index de5acd3249..e8ad8164bb 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.32", + "version": "1.0.33-next.0", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 7ff6862d2f..d593501998 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,14 @@ # e2e-test +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.16-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + ## 0.2.16 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index a77bb8c8a1..aab8e4e99f 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.16", + "version": "0.2.17-next.0", "private": true, "backstage": { "role": "cli" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index b9571091a8..53661a3b7c 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/frontend-app-api +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.7.0 ### Minor Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 2e350b5a98..8789f9fbeb 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.7.0", + "version": "0.7.1-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index f12de020f5..0d054e2549 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-plugin-api +## 0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.6.5 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 2dedd6fc12..1a21344f8d 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.5", + "version": "0.6.6-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index b2abbd9c2d..b8dfd8335d 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-test-utils +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.1-next.0 + - @backstage/test-utils@1.5.6-next.0 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + ## 0.1.7 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 4845e7e9b1..8f849be064 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.1.7", + "version": "0.1.8-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 251ddb28de..4e4707d7f5 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/repo-tools +## 0.9.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + ## 0.9.0 ### Minor Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 5c2e5e859c..dd0ba95ee7 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.9.0", + "version": "0.9.1-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 0bb564782c..9fbc781d64 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,24 @@ # techdocs-cli-embedded-app +## 0.2.97-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/theme@0.5.6-next.0 + - @backstage/cli@0.26.6-next.0 + - @backstage/app-defaults@1.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/test-utils@1.5.6-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog@1.20.1-next.0 + ## 0.2.96 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index c6f776015c..822b784bf3 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.96", + "version": "0.2.97-next.0", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index c1c15bb78c..a154513749 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @techdocs/cli +## 1.8.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-techdocs-node@1.12.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + ## 1.8.11 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 5af004aeb0..3edd15e192 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.11", + "version": "1.8.12-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 4c8e0c1173..6176df8d17 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/test-utils +## 1.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + ## 1.5.5 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index d48b652c43..136be68fe6 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.5.5", + "version": "1.5.6-next.0", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 397b6c80f5..0cd38f9265 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.5.6-next.0 + +### Patch Changes + +- 702fa7d: Internal refactor to fix an issue where the MUI 5 `v5-` class prefixing gets removed by tree shaking. + ## 0.5.4 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index 698d4f567c..8cdb23c1ec 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.5.4", + "version": "0.5.6-next.0", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 92eb1b44f1..2d7acf4cd0 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-api-docs +## 0.11.6-next.0 + +### Patch Changes + +- 96cd13e: `DefaultApiExplorerPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-permission-react@0.4.22 + ## 0.11.5 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 398407e33f..6ece06888c 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.5", + "version": "0.11.6-next.0", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin" diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index c33d774b1a..77de6588f4 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-app-backend +## 0.3.68-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 82c2b90: Restore the support of external config schema in the router of the `app-backend` plugin, which was broken in release `1.26.0`. + This support is critical for dynamic frontend plugins to have access to their config values. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-app-node@0.1.19-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.3.66 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 4d53cdb407..177b91a202 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.66", + "version": "0.3.68-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 75fc04fbdf..982c5e60e8 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-node +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config-loader@1.8.0 + ## 0.1.18 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index cbaca9f3d1..f7e4b3ec30 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-node", "description": "Node.js library for the app plugin", - "version": "0.1.18", + "version": "0.1.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index bf39546c90..3d51461221 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-visualizer +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + ## 0.1.6 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index a3e9d89e3b..9a3ec6f838 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.6", + "version": "0.1.7-next.0", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin" diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index f75da3056e..e0075771b3 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index 0b0f949c44..50a80d138d 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", "description": "The atlassian-provider backend module for the auth plugin.", - "version": "0.1.10", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index f3c1594df9..1da1830fab 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + ## 0.1.10 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index e699c13dfd..3d2f4f890b 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", "description": "The aws-alb provider module for the Backstage auth backend.", - "version": "0.1.10", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 901f88b20a..ea8bff0ce9 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + ## 0.1.1 ### Patch Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index bec9f86224..5f5a501136 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.1.1", + "version": "0.1.2-next.0", "description": "The azure-easyauth-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md index a3787cfdc0..5a42e0443b 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 3d16dc4bce..489123cba8 100644 --- a/plugins/auth-backend-module-bitbucket-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-provider", - "version": "0.1.1", + "version": "0.1.2-next.0", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index d8e5dfe4d5..e532415c6b 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.1 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index 7fe161480b..a332207890 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.1.1", + "version": "0.1.2-next.0", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index 89f6dd9805..14cd1f8d8a 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.13 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 603fcc64c7..f33019f9de 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", "description": "A GCP IAP auth provider module for the Backstage auth backend", - "version": "0.2.13", + "version": "0.2.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index a892924c64..fda8aa5e74 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.15 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 2a45c2ed78..34a0b3a81f 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "version": "0.1.15", + "version": "0.1.16-next.0", "description": "The github-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 201fa5901b..39a0552ff9 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.15 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 146c26fffd..928ed1a067 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", "description": "The gitlab-provider backend module for the auth plugin.", - "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/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 46ea104253..1c5f3945f3 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.15 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 35814282ec..1531294127 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", "description": "A Google auth provider module for the Backstage auth backend", - "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/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 91565c88fd..ae130e0fe2 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + ## 0.1.4 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index e27c19eb5d..86276daea3 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.1.4", + "version": "0.1.5-next.0", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index 83c3bb229d..8e73f1b0dd 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index 9e2719acc3..7f414bb986 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", "description": "The microsoft-provider backend module for the auth plugin.", - "version": "0.1.13", + "version": "0.1.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index dbb970e028..58597eb968 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.15 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 6aecfd6c48..cb73eb75f6 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", "description": "The oauth2-provider backend module for the auth plugin.", - "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/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index e5366af8b6..0735cf7fbb 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + ## 0.1.11 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json index 0f3063db34..50d833a3f4 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", "description": "The oauth2-proxy-provider backend module for the auth plugin.", - "version": "0.1.11", + "version": "0.1.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index e8716f0ce1..191f459689 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-auth-backend@0.22.6-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.1.9 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 34bb282ba5..cbdffb0f02 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", "description": "The oidc-provider backend module for the auth plugin.", - "version": "0.1.9", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 8378a83e7c..c5bdfac9d4 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + ## 0.0.11 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index b8a679b0b0..ae5e3ad62c 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", "description": "The okta-provider backend module for the auth plugin.", - "version": "0.0.11", + "version": "0.0.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index e9631b54cc..9aef5a38ba 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + ## 0.1.12 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 227055d366..536c97983d 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", "description": "The pinniped-provider backend module for the auth plugin.", - "version": "0.1.12", + "version": "0.1.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 97753a12d2..ba028ce0ce 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/catalog-model@1.5.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json index 64af928848..8933b96853 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/package.json +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", - "version": "0.1.10", + "version": "0.1.11-next.0", "description": "The vmware-cloud-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 2b161d638a..fd910b9377 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend +## 0.22.6-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.12-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.12-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.22.5 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 0797145855..cccb390b5b 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.5", + "version": "0.22.6-next.0", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin" diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index ecfd0c78b6..b60cf12574 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-auth-node +## 0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.4.13 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 8b0e7f87c3..5fad67b237 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.4.13", + "version": "0.4.14-next.0", "backstage": { "role": "node-library" }, diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index 46ddcc9a51..dea91fda48 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-react +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + ## 0.1.2 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 7b85295f48..9dbc8c1fa6 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.2", + "version": "0.1.3-next.0", "description": "Web library for the auth plugin", "backstage": { "role": "web-library" diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index cf7dc39048..13280d414a 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.3.13 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index ba9a299fad..69d692a4cc 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.3.13", + "version": "0.3.14-next.0", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 649c15872a..205c5a2b17 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.39-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.1.38 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index e5b47db18c..221e0058eb 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.1.38", + "version": "0.1.39-next.0", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index 2d63fdeadd..30ab41b756 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/backend-openapi-utils@0.1.12-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.2.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index af9a299848..9795f58821 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.2.1", + "version": "0.2.2-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 1fa58406c4..c02efbc491 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.6-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.19 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.2.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index b821a1e01d..667bdf4a23 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.2.5", + "version": "0.2.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index ffb79454cf..534be4549d 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.1.32 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 684b5fa95a..d9e46ebec4 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.32", + "version": "0.1.33-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 008b18882b..3903d46108 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.1.19 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index f0f3b1722f..b832dfafab 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.1.19", + "version": "0.1.20-next.0", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 2b5f22e034..206c04b47c 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.1.35 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 1e9ff4284c..130ba40687 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.35", + "version": "0.1.36-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 99f073203c..6a9793f161 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-backend-module-github@0.6.2-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/config@1.2.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 9f73d2a052..a6d2f7cfcc 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.1.13", + "version": "0.1.14-next.0", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 09735e37ca..b25241c192 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.6.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index d71303629a..4a53a7d389 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.6.1", + "version": "0.6.2-next.0", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 793b10fd8b..58f9136ac5 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.0.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index d0069fc968..91972cd3df 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.0.1", + "version": "0.0.2-next.0", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index ee1ceb70b7..d4fc84ed2a 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.3.16 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 109dee369e..c4d4c07131 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.3.16", + "version": "0.3.17-next.0", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index b20c236f55..5051cf43db 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + ## 0.4.23 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 8f6016a228..ab4bacf252 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.4.23", + "version": "0.4.24-next.0", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 917f895046..6410c0bde3 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-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.23 + ## 0.5.34 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 2b447ea7ec..8bfa0905f6 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.5.34", + "version": "0.5.35-next.0", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 526a8609ac..63b43005c7 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.5.26 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index b12afe2f01..f031e85329 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.5.26", + "version": "0.5.27-next.0", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index a0f8b3945d..b02043655b 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-catalog-backend@1.23.0-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + ## 0.1.36 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 40975fc254..7e4d72a5f1 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.1.36", + "version": "0.1.37-next.0", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index e49b8fdfa2..1fb2d5dc75 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.1.24 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index d12990b54d..43ec390541 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.1.24", + "version": "0.1.25-next.0", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index e2a2e7f299..290f7acd35 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-scaffolder-common@1.5.2 + ## 0.1.16 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 5f7a22b8dc..f1842779e5 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.1.16", + "version": "0.1.17-next.0", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 16b105df8d..61d8ab845b 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.1 + - @backstage/plugin-permission-common@0.7.13 + ## 0.4.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index c1df39fc6c..5d78cde6ae 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.4.5", + "version": "0.4.6-next.0", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 3934bb495f..22ca3b4f89 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-backend +## 1.23.0-next.0 + +### Minor Changes + +- c7528b0: Pass through `EventsService` too in the new backend system + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 1779188: Start using the `isDatabaseConflictError` helper from the `@backstage/backend-plugin-api` package in order to avoid dependency with the soon to deprecate `@backstage/backend-common` package. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/backend-openapi-utils@0.1.12-next.0 + - @backstage/plugin-catalog-node@1.12.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@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-common@0.7.13 + ## 1.22.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index bdfcecf2b1..a924881a4d 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.22.0", + "version": "1.23.0-next.0", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin" diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 9d5a638ad7..8618b6bc73 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-graph +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.4.5 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 4d2ac4b393..af79937eec 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.5", + "version": "0.4.6-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 8bc9f0914e..a72fb070da 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-import +## 0.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-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.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.11.0 ### Minor Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 822ed6f395..1aec15f541 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.11.0", + "version": "0.11.1-next.0", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 8948ad6f98..0736754930 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-node +## 1.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-permission-node@0.7.30-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.23 + - @backstage/plugin-permission-common@0.7.13 + ## 1.12.0 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index da6a67f163..48bbf806c8 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.12.0", + "version": "1.12.1-next.0", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library" diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index ab6f2290ba..afd00efde9 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-react +## 1.12.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration-react@1.1.27 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + ## 1.12.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 6f0094f0c7..6e341f5700 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.12.0", + "version": "1.12.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 989a852b55..76592e41fd 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + ## 0.2.4 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 059dc8802b..eae8d36e9a 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.2.4", + "version": "0.2.5-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index b2d036e914..23d7fee635 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog +## 1.20.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration-react@1.1.27 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/plugin-search-common@1.2.11 + ## 1.20.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index bad4466146..44283c44dc 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.20.0", + "version": "1.20.1-next.0", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 4d6609477c..6f619ef4ea 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-config-schema +## 0.1.56-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.1.55 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index bbf1e786a1..bef65c44cb 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.55", + "version": "0.1.56-next.0", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin" diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 902a43c880..d0c2f7d6fc 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-devtools-backend +## 0.3.5-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-common@0.7.13 + ## 0.3.4 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 0852a813ed..3b6ee4af31 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.4", + "version": "0.3.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index ee7fd659bb..ceb3dc0f0c 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-devtools +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-react@0.4.22 + ## 0.1.14 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index c711d941c6..2daa585aa1 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.14", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 1398d95e4a..42ce92265e 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + ## 0.3.4 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 0e889a2f8e..1a4951ae2f 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.3.4", + "version": "0.3.5-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index d9c8e91140..ce1deef4c1 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 9264fe2374..52702cee86 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-azure", - "version": "0.2.4", + "version": "0.2.5-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index b2734012c0..23325256a5 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 5dc4d0147a..d29d990910 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-cloud", - "version": "0.2.4", + "version": "0.2.5-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index 5a1134e692..8cd3cba606 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 61d0fafd53..fa48481a49 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gerrit", - "version": "0.2.4", + "version": "0.2.5-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 88b0b1634d..565855d6b5 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-github +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index d234a7e01b..b4644a9ca0 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.2.4", + "version": "0.2.5-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index b6bb80f18f..74461c9ef0 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index f793455a5d..3a3f5f3fcb 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.2.4", + "version": "0.2.5-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index eeb52a1d29..feedc5afa2 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.1.28 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index b96b7afe7f..fdf97ec741 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.28", + "version": "0.1.29-next.0", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library" diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 0862263896..84337dec34 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 1cd3f909cc..5bbcd48919 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.5", + "version": "0.3.6-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 5b907547d8..cc592fc821 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-node +## 0.3.5-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + ## 0.3.4 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 69028e9860..e9e95ae414 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.3.4", + "version": "0.3.5-next.0", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library" diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index d152236a11..1350ca84c5 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @internal/plugin-todo-list-backend +## 1.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/errors@1.2.4 + ## 1.0.27 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index fd2ac61096..60aa287efb 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.27", + "version": "1.0.28-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index a86ffe7c60..c55771ec4d 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-todo-list +## 1.0.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + ## 1.0.27 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 4bd01f6e32..3585de7056 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.27", + "version": "1.0.28-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 6013cad734..77eebf239e 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-home-react +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + ## 0.1.13 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 3853ef0f85..1cda383986 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.13", + "version": "0.1.14-next.0", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library" diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 9543096cd0..0e8d21af3f 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-home +## 0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-home-react@0.1.14-next.0 + ## 0.7.4 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 79003a2ad7..43d3cfc77a 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.4", + "version": "0.7.5-next.0", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index bc447b62e0..6b742d4f4d 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-kubernetes-backend +## 0.17.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-kubernetes-node@0.1.13-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-catalog-node@1.12.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-kubernetes-common@0.7.6 + - @backstage/plugin-permission-common@0.7.13 + ## 0.17.1 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 7589a453a7..678e68816f 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.17.1", + "version": "0.17.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index aad774818d..fe667ca054 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.6 + - @backstage/plugin-kubernetes-react@0.3.6-next.0 + ## 0.0.11 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index db9d08b5d7..67527562a6 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.11", + "version": "0.0.12-next.0", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 730d43b95c..fd1481b0a1 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes-node +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.1.12 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index dbe0a35d44..fbc187750a 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.12", + "version": "0.1.13-next.0", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library" diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 76abb6a50a..7d94edb692 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-react +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.6 + ## 0.3.5 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 0991845ebd..47f6cd0c25 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-react", "description": "Web library for the kubernetes-react plugin", - "version": "0.3.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index abd18e5e21..fcfbfdb6d2 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes +## 0.11.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.6 + - @backstage/plugin-kubernetes-react@0.3.6-next.0 + ## 0.11.10 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index fd02de4137..bd47686e63 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.10", + "version": "0.11.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 41bb1d2d9e..e2b1e0cea4 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-notifications-backend-module-email +## 0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-notifications-node@0.1.5-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.3 + ## 0.0.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 892c8c9156..d94048978e 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.0.1", + "version": "0.0.2-next.0", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 26762f5b2a..923696afce 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-notifications-backend +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-notifications-node@0.1.5-next.0 + - @backstage/plugin-signals-node@0.1.5-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-notifications-common@0.0.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 6e6c0e04cb..3d809ba11b 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.2.1", + "version": "0.2.2-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 86813090da..eca0358484 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-notifications-node +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-signals-node@0.1.5-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.3 + ## 0.1.4 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 9f764c732b..1f3760f5c6 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.1.4", + "version": "0.1.5-next.0", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library" diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 278ac77062..08affa74d9 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-notifications +## 0.2.2-next.0 + +### Patch Changes + +- 7f02684: Do not always show scrollbars in notification description +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-react@0.0.3 + ## 0.2.1 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 8307103942..6c975c8557 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.1", + "version": "0.2.2-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 853a4e76e3..e3448085c4 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org-react +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.1.24 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index dd9bc64f5a..b6f8a03996 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.24", + "version": "0.1.25-next.0", "backstage": { "role": "web-library" }, diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 885f03cb3d..a8a471e797 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-org +## 0.6.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.6.25 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 5c9580ee0b..927600658d 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.25", + "version": "0.6.26-next.0", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin" diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index d2ae73f5f4..f234515826 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-permission-common@0.7.13 + ## 0.1.15 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 6e08904940..52c448a24a 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.1.15", + "version": "0.1.16-next.0", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 6548fdfac1..46c70631c1 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-permission-backend +## 0.5.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + ## 0.5.42 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index e039cd1898..c09007b817 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.42", + "version": "0.5.43-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 8b145de147..756b93a37c 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-permission-node +## 0.7.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + ## 0.7.29 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index bf6afc9e34..254ef17d00 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.29", + "version": "0.7.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 80e2f2e241..2db699508f 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-proxy-backend +## 0.5.0-next.0 + +### Minor Changes + +- 88480e4: **BREAKING**: The proxy backend plugin is now protected by Backstage auth, by + default. Unless specifically configured (see below), all proxy endpoints will + reject requests immediately unless a valid Backstage user or service token is + passed along with the request. This aligns the proxy with how other Backstage + backends behave out of the box, and serves to protect your upstreams from + unauthorized access. + + A proxy configuration section can now look as follows: + + ```yaml + proxy: + endpoints: + '/pagerduty': + target: https://api.pagerduty.com + credentials: require # NEW! + headers: + Authorization: Token token=${PAGERDUTY_TOKEN} + ``` + + There are three possible `credentials` settings at this point: + + - `require`: Callers must provide Backstage user or service credentials with + each request. The credentials are not forwarded to the proxy target. + - `forward`: Callers must provide Backstage user or service credentials with + each request, and those credentials are forwarded to the proxy target. + - `dangerously-allow-unauthenticated`: No Backstage credentials are required to + access this proxy target. The target can still apply its own credentials + checks, but the proxy will not help block non-Backstage-blessed callers. If + you also add `allowedHeaders: ['Authorization']` to an endpoint configuration, + then the Backstage token (if provided) WILL be forwarded. + + The value `dangerously-allow-unauthenticated` was the old default. + + The value `require` is the new default, so requests that were previously + permitted may now start resulting in `401 Unauthorized` responses. If you have + `backend.auth.dangerouslyDisableDefaultAuthPolicy` set to `true`, this does not + apply; the proxy will behave as if all endpoints were set to + `dangerously-allow-unauthenticated`. + + If you have proxy endpoints that require unauthenticated access still, please + add `credentials: dangerously-allow-unauthenticated` to their declarations in + your app-config. + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + ## 0.4.16 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 156bd74cb9..d92dba9069 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.4.16", + "version": "0.5.0-next.0", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index c2418d6309..15102f83a3 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index bc6b4a45d0..b2055885d9 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.1.10", + "version": "0.1.11-next.0", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 9a1340c1ad..f29cbd7bca 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index abde43fb2f..5743aa6d95 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.1.8", + "version": "0.1.9-next.0", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index fc245e48ba..681e427302 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 2a9fcc3f9d..f6adc516c1 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.1.8", + "version": "0.1.9-next.0", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 97a57ff28f..42cea24286 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.2.8 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 355ca41844..e35b0a6e11 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.2.8", + "version": "0.2.9-next.0", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 77152b9659..e152ffeba6 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.2.19 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 7cca3be249..67b85ef18e 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.19", + "version": "0.2.20-next.0", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index a6c6b24a07..c3e2893dc6 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + ## 0.2.42 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index b0f137ffab..2d750a1086 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.42", + "version": "0.2.43-next.0", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index 3daf8fc28a..e9b9d2b411 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 1d9e003cfd..fe14a92a14 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.1.10", + "version": "0.1.11-next.0", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 6ec5592bc5..373b06a638 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 1d8ad190bc..0d6baeaaa0 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.8", + "version": "0.1.9-next.0", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 40c8349399..a375e2202b 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.2.9-next.0 + +### Patch Changes + +- f145a04: Added handling for dry run to githubPullRequest and githubWebhook and added tests for this functionality +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.2.8 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 56dbd291a3..6338947116 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.2.8", + "version": "0.2.9-next.0", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 829e2122f3..f7d18d703a 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + ## 0.4.0 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 383ef0e79d..b91b36bd55 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.4.0", + "version": "0.4.1-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index d5d3902191..8e5994dd23 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-notifications-node@0.1.5-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-notifications-common@0.0.3 + ## 0.0.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 07cb5e9bb1..fcebc0c511 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.0.1", + "version": "0.0.2-next.0", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 64c1c79d7c..1c174579bb 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + ## 0.4.35 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 0d822764dd..62ba9ff77c 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.35", + "version": "0.4.36-next.0", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index fa5b12ae0e..6f5d470dab 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.26 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 1f2e48ad59..a44de6705a 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.26", + "version": "0.1.27-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index cb1a73f9f7..11bc8462c7 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.1.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/types@1.1.1 + ## 0.3.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 9a2fb1fdb1..dd05333b0b 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.3.1", + "version": "0.3.2-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 05be28c1ae..477470b8c9 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-scaffolder-backend +## 1.22.8-next.0 + +### Patch Changes + +- 7d30d95: Fixing issue with log meta fields possibly being circular refs +- d617103: Updating the logger redaction message to something less dramatic +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.0 + - @backstage/plugin-catalog-node@1.12.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@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-scaffolder-common@1.5.2 + ## 1.22.6 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f748ccb92d..0caa20c410 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.22.6", + "version": "1.22.8-next.0", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 72804941e4..770f78ef77 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.5-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-test-utils@0.3.9-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.0 + - @backstage/types@1.1.1 + ## 0.1.4 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 8686592f7a..cd0cac649d 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.4", + "version": "0.1.5-next.0", "backstage": { "role": "node-library" }, diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 5c6fe0f6cc..0dc1ffeec8 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-node +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.2 + ## 0.4.4 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index af26f204dd..5f2834320f 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.4", + "version": "0.4.5-next.0", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library" diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index c445b76d06..84c442c040 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-scaffolder-react +## 1.8.6-next.0 + +### Patch Changes + +- 86dc29d: Links that are rendered in the markdown in the `ScaffolderField` component are now opened in new tabs. +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.2 + ## 1.8.5 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 9187d9259d..0a8d229bfe 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.8.5", + "version": "1.8.6-next.0", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library" diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index c4949715d7..120d239ea1 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-scaffolder +## 1.20.1-next.0 + +### Patch Changes + +- 612a453: Change owner to project for azure host +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-scaffolder-common@1.5.2 + ## 1.20.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index c11f1a163a..73e231644a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.20.0", + "version": "1.20.1-next.0", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin" diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index d0c84a7617..3cd732f2b9 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-search-backend-module-catalog +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-catalog-node@1.12.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.23 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.24 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index f7eca60fbd..41828a7475 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.24", + "version": "0.1.25-next.0", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 5bf9aa19ea..73aa7aee2c 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.4.2-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + ## 1.4.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 1cd9bfc854..888b89e1bc 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.4.1", + "version": "1.4.2-next.0", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index fc8ff4b307..27137acc34 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-explore +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.24 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 28ba6945c3..3e32174a2a 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.1.24", + "version": "0.1.25-next.0", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index ba6674606d..9d4fd62f82 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.28-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + ## 0.5.27 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 6d61b7a050..899abcee31 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.27", + "version": "0.5.28-next.0", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index c56c40ebe4..1d643c7180 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.11 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index 5ae74d37bc..da22880943 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.1.11", + "version": "0.1.12-next.0", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 569a73c232..bd810f5208 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-techdocs-node@1.12.5-next.0 + - @backstage/plugin-catalog-node@1.12.1-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.23 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.23 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index c27fe52d93..5df953cbf0 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.23", + "version": "0.1.24-next.0", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 74ba4c86c1..769f8e5785 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-search-backend-node +## 1.2.24-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- 5b6f979: Split backend search plugin startup into "init" and "start" stages to ensure necessary initialization has happened before startup +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.0 + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 1.2.22 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index cd2981d792..50f59089b2 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.2.22", + "version": "1.2.24-next.0", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 0a80301ede..c116515a7a 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-search-backend +## 1.5.10-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- 5b6f979: Split backend search plugin startup into "init" and "start" stages to ensure necessary initialization has happened before startup +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.0 + - @backstage/backend-defaults@0.2.19-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/repo-tools@0.9.1-next.0 + - @backstage/plugin-permission-node@0.7.30-next.0 + - @backstage/backend-openapi-utils@0.1.12-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 1.5.8 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 06a6b5a3ff..523abc24c9 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.8", + "version": "1.5.10-next.0", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin" diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index 046532facf..806c6c1a24 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search-react +## 1.7.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.11 + ## 1.7.11 ### Patch Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 1e6b2b0e8e..76d0881510 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.7.11", + "version": "1.7.12-next.0", "backstage": { "role": "web-library" }, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index b738d853f8..98c9852acc 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search +## 1.4.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-search-common@1.2.11 + ## 1.4.11 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 1a9a09a5fe..c8a46d9555 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.11", + "version": "1.4.12-next.0", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin" diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index f6eeac9bf4..e748bcd493 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-signals-backend +## 0.1.5-next.0 + +### Patch Changes + +- 6a576dc: Replace the usage of `getVoidLogger` with `mockServices.logger.mock` in order to remove the dependency with the soon-to-be-deprecated `backend-common` package. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/plugin-signals-node@0.1.5-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + ## 0.1.4 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 846d77d661..8a2ec3373b 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.4", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index e26991c787..ab160c0c8f 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-signals-node +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + ## 0.1.4 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index 4d1ad9e1ef..f93947ae4b 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-signals-node", "description": "Node.js library for the signals plugin", - "version": "0.1.4", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index 971a849a03..9774b783db 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-signals +## 0.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.3 + ## 0.0.6 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index ec618415a8..872f4f2f38 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.6", + "version": "0.0.7-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 8a4b3d1eb8..f249589bc8 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.6-next.0 + - @backstage/test-utils@1.5.6-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-catalog@1.20.1-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.0.32 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 02161159e9..2f5034b899 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.32", + "version": "1.0.33-next.0", "backstage": { "role": "web-library" }, diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index d4d18976b0..2764510f3c 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-techdocs-backend +## 1.10.6-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.0 + - @backstage/plugin-techdocs-node@1.12.5-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@1.11.0 + - @backstage/plugin-catalog-common@1.0.23 + - @backstage/plugin-permission-common@0.7.13 + ## 1.10.5 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 89e08878b6..df8cd6e096 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.5", + "version": "1.10.6-next.0", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin" diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 2ab0cef022..a2bfb1ca07 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + ## 1.1.10 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 2aaca2c4e0..e7f4ff9b93 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.10", + "version": "1.1.11-next.0", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module" diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 59a0cc2055..c7d0223508 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-techdocs-node +## 1.12.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.11.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + ## 1.12.4 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 7d4c83545d..08b52b45b0 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.4", + "version": "1.12.5-next.0", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library" diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 4b76fe42e4..ad619374e3 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-react +## 1.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/version-bridge@1.0.8 + ## 1.2.4 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 283e3afcff..b6420d688b 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.2.4", + "version": "1.2.5-next.0", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 7e8f3056e5..a98d4cd523 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-techdocs +## 1.10.6-next.0 + +### Patch Changes + +- 654af4a: mkdocs-material have updated their CSS variable template, and a few are unset in Backstage. This patch adds the missing variables to ensure coverage. +- 96cd13e: `TechDocsIndexPage` now accepts an optional `ownerPickerMode` for toggling the behavior of the `EntityOwnerPicker`, exposing a new mode `` particularly suitable for larger catalogs. In this new mode, `EntityOwnerPicker` will display all the users and groups present in the catalog. +- e40bd9a: Fixed bug in CopyToClipboardButton component where positioning of the "Copy to clipboard" button in techdocs code snippets was broken in some cases +- 1256d88: Fix weird opening behaviour of the component. +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/plugin-search-react@1.7.12-next.0 + - @backstage/plugin-techdocs-react@1.2.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/integration@1.11.0 + - @backstage/integration-react@1.1.27 + - @backstage/plugin-auth-react@0.1.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-search-common@1.2.11 + ## 1.10.5 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index bc9c4c8022..845b65cd8f 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.10.5", + "version": "1.10.6-next.0", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin" diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index f689d8090f..9402f0d688 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-user-settings-backend +## 0.2.18-next.0 + +### Patch Changes + +- 8869b8e: Updated local development setup. +- Updated dependencies + - @backstage/backend-common@0.22.1-next.0 + - @backstage/backend-plugin-api@0.6.19-next.0 + - @backstage/plugin-auth-node@0.4.14-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.17 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 1016d59256..b3210d8357 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.2.17", + "version": "0.2.18-next.0", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin" diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 85930ec041..6a611c1c2f 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-user-settings +## 0.8.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/core-app-api@1.12.5 + - @backstage/core-compat-api@0.2.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.6-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.8.6 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index b3f0f244c8..03617c88b1 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.6", + "version": "0.8.7-next.0", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin" diff --git a/yarn.lock b/yarn.lock index 8cafbcecd9..d006daa23c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3589,7 +3589,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": +"@backstage/catalog-client@^1.6.5, @backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": version: 0.0.0-use.local resolution: "@backstage/catalog-client@workspace:packages/catalog-client" dependencies: @@ -3602,7 +3602,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@^1.4.3, @backstage/catalog-model@^1.4.5, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@^1.4.3, @backstage/catalog-model@^1.4.5, @backstage/catalog-model@^1.5.0, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3854,7 +3854,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@^1.1.1, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@^1.1.1, @backstage/config@^1.2.0, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -3926,7 +3926,107 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@^0.14.4, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@npm:^0.13.10": + version: 0.13.10 + resolution: "@backstage/core-components@npm:0.13.10" + dependencies: + "@backstage/config": ^1.1.1 + "@backstage/core-plugin-api": ^1.8.2 + "@backstage/errors": ^1.2.3 + "@backstage/theme": ^0.5.0 + "@backstage/version-bridge": ^1.0.7 + "@date-io/core": ^1.3.13 + "@material-table/core": ^3.1.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^23.0.0 + "@types/react": ^16.13.1 || ^17.0.0 + "@types/react-sparklines": ^1.7.0 + "@types/react-text-truncate": ^0.14.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + linkify-react: 4.1.3 + linkifyjs: 4.1.3 + lodash: ^4.17.21 + pluralize: ^8.0.0 + qs: ^6.9.4 + rc-progress: 3.5.1 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-idle-timer: 5.6.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-text-truncate: ^0.19.0 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.11 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.10.0 + zod: ^3.22.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 + languageName: node + linkType: hard + +"@backstage/core-components@npm:^0.14.4, @backstage/core-components@npm:^0.14.7": + version: 0.14.7 + resolution: "@backstage/core-components@npm:0.14.7" + dependencies: + "@backstage/config": ^1.2.0 + "@backstage/core-plugin-api": ^1.9.2 + "@backstage/errors": ^1.2.4 + "@backstage/theme": ^0.5.4 + "@backstage/version-bridge": ^1.0.8 + "@date-io/core": ^1.3.13 + "@material-table/core": ^3.1.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^24.0.0 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + "@types/react-sparklines": ^1.7.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + linkify-react: 4.1.3 + linkifyjs: 4.1.3 + lodash: ^4.17.21 + pluralize: ^8.0.0 + qs: ^6.9.4 + rc-progress: 3.5.1 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-idle-timer: 5.7.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.11 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.10.0 + zod: ^3.22.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: e3906347c197d741dbff24d20af5cd0117e4bf1667c34a4a3a70d8c0f3c0c92bd768b53c417ca78f8b87560a3623f497938f531be86c8727f83822a11e9aa4e5 + languageName: node + linkType: hard + +"@backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -3997,57 +4097,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.13.10": - version: 0.13.10 - resolution: "@backstage/core-components@npm:0.13.10" - dependencies: - "@backstage/config": ^1.1.1 - "@backstage/core-plugin-api": ^1.8.2 - "@backstage/errors": ^1.2.3 - "@backstage/theme": ^0.5.0 - "@backstage/version-bridge": ^1.0.7 - "@date-io/core": ^1.3.13 - "@material-table/core": ^3.1.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^23.0.0 - "@types/react": ^16.13.1 || ^17.0.0 - "@types/react-sparklines": ^1.7.0 - "@types/react-text-truncate": ^0.14.0 - ansi-regex: ^6.0.1 - classnames: ^2.2.6 - d3-selection: ^3.0.0 - d3-shape: ^3.0.0 - d3-zoom: ^3.0.0 - dagre: ^0.8.5 - linkify-react: 4.1.3 - linkifyjs: 4.1.3 - lodash: ^4.17.21 - pluralize: ^8.0.0 - qs: ^6.9.4 - rc-progress: 3.5.1 - react-helmet: 6.1.0 - react-hook-form: ^7.12.2 - react-idle-timer: 5.6.2 - react-markdown: ^8.0.0 - react-sparklines: ^1.7.0 - react-syntax-highlighter: ^15.4.5 - react-text-truncate: ^0.19.0 - react-use: ^17.3.2 - react-virtualized-auto-sizer: ^1.0.11 - react-window: ^1.8.6 - remark-gfm: ^3.0.1 - zen-observable: ^0.10.0 - zod: ^3.22.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 - languageName: node - linkType: hard - "@backstage/core-plugin-api@^1.8.2, @backstage/core-plugin-api@^1.9.2, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" @@ -4195,6 +4244,26 @@ __metadata: languageName: unknown linkType: soft +"@backstage/frontend-plugin-api@npm:^0.6.5": + version: 0.6.5 + resolution: "@backstage/frontend-plugin-api@npm:0.6.5" + dependencies: + "@backstage/core-components": ^0.14.7 + "@backstage/core-plugin-api": ^1.9.2 + "@backstage/types": ^1.1.1 + "@backstage/version-bridge": ^1.0.8 + "@material-ui/core": ^4.12.4 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + lodash: ^4.17.21 + zod: ^3.22.4 + zod-to-json-schema: ^3.21.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: be880b5a3bb86d4e2c32a90ca64265b529901dabcdcbf5a87b5cbdfd68fc347297359550da195034e671e074db9db7c659d663d4fd46ed3836896bd1878fae2f + languageName: node + linkType: hard + "@backstage/frontend-plugin-api@workspace:^, @backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api": version: 0.0.0-use.local resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" @@ -4258,7 +4327,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": +"@backstage/integration-react@^1.1.27, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -5336,7 +5405,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@^1.0.20, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@^1.0.20, @backstage/plugin-catalog-common@^1.0.23, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -5445,7 +5514,43 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@^1.11.3, @backstage/plugin-catalog-react@^1.9.3, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@npm:^1.11.3, @backstage/plugin-catalog-react@npm:^1.9.3": + version: 1.12.0 + resolution: "@backstage/plugin-catalog-react@npm:1.12.0" + dependencies: + "@backstage/catalog-client": ^1.6.5 + "@backstage/catalog-model": ^1.5.0 + "@backstage/core-components": ^0.14.7 + "@backstage/core-plugin-api": ^1.9.2 + "@backstage/errors": ^1.2.4 + "@backstage/frontend-plugin-api": ^0.6.5 + "@backstage/integration-react": ^1.1.27 + "@backstage/plugin-catalog-common": ^1.0.23 + "@backstage/plugin-permission-common": ^0.7.13 + "@backstage/plugin-permission-react": ^0.4.22 + "@backstage/types": ^1.1.1 + "@backstage/version-bridge": ^1.0.8 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^24.0.0 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + classnames: ^2.2.6 + lodash: ^4.17.21 + material-ui-popup-state: ^1.9.3 + qs: ^6.9.4 + react-use: ^17.2.4 + yaml: ^2.0.0 + zen-observable: ^0.10.0 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: 8079c6c1f4c5b07df5cfb2873f4c3ba16759a3a1ac36a1e1ff61fb428cece5baca40b5c2bc41a2d402ad9a60e73fd545c0a95eec274189423f40a04551a5f337 + languageName: node + linkType: hard + +"@backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -6286,7 +6391,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": +"@backstage/plugin-permission-common@^0.7.13, @backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" dependencies: @@ -6324,7 +6429,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": +"@backstage/plugin-permission-react@^0.4.22, @backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" dependencies: @@ -7608,7 +7713,23 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@^0.5.0, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@npm:^0.5.0, @backstage/theme@npm:^0.5.4": + version: 0.5.5 + resolution: "@backstage/theme@npm:0.5.5" + dependencies: + "@emotion/react": ^11.10.5 + "@emotion/styled": ^11.10.5 + "@mui/material": ^5.12.2 + peerDependencies: + "@material-ui/core": ^4.12.2 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + checksum: a5ba7b39d41773a4a73a07d1a9a6bcf0815835a196a31c1ec1d5eac61ec801bfe875f31823a6ae6aa5033b21bd04b4fa692fd3fddc71a12e2126b1d222738b34 + languageName: node + linkType: hard + +"@backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -7639,7 +7760,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": +"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@^1.0.8, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": version: 0.0.0-use.local resolution: "@backstage/version-bridge@workspace:packages/version-bridge" dependencies: From a1218fc1718f48197c1ac044556b7b7de36c4cb4 Mon Sep 17 00:00:00 2001 From: Frank Kong Date: Tue, 21 May 2024 10:56:23 -0400 Subject: [PATCH 500/567] chore: address review comments for docs Signed-off-by: Frank Kong --- ...ctions.md => authorizing-scaffolder-template-details.md} | 6 +++--- microsite/docusaurus.config.ts | 4 ++++ microsite/sidebars.json | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) rename docs/features/software-templates/{authorizing-scaffolder-tasks-parameters-steps-and-actions.md => authorizing-scaffolder-template-details.md} (97%) diff --git a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-scaffolder-template-details.md similarity index 97% rename from docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md rename to docs/features/software-templates/authorizing-scaffolder-template-details.md index 27696af1a9..127677757c 100644 --- a/docs/features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-scaffolder-template-details.md @@ -1,7 +1,7 @@ --- -id: authorizing-scaffolder-tasks-parameters-steps-and-actions -title: 'Authorizing scaffolder tasks parameters, steps and actions' -description: How to authorize part of a template and authorize scaffolder task access +id: authorizing-scaffolder-template-details +title: 'Authorizing scaffolder tasks, parameters, steps, and actions' +description: How to authorize parts of a template and authorize scaffolder task access --- The scaffolder plugin integrates with the Backstage [permission framework](../../permissions/overview.md), which allows you to control access to certain parameters and steps in your templates based on the user executing the template. It also allows you to control access to scaffolder tasks. diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index e697c5024f..75a67406b6 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -171,6 +171,10 @@ const config: Config = { from: '/docs/getting-started/configuration', to: '/docs/getting-started/#next-steps', }, + { + from: '/docs/features/software-templates/authorizing-parameters-steps-and-actions', + to: '/docs/features/software-templates/authorizing-scaffolder-template-details', + }, ], }, ], diff --git a/microsite/sidebars.json b/microsite/sidebars.json index bfb9215705..a3d9f4dbec 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -130,7 +130,7 @@ "features/software-templates/writing-tests-for-actions", "features/software-templates/writing-custom-field-extensions", "features/software-templates/writing-custom-step-layouts", - "features/software-templates/authorizing-scaffolder-tasks-parameters-steps-and-actions", + "features/software-templates/authorizing-scaffolder-template-details", "features/software-templates/migrating-to-rjsf-v5", "features/software-templates/migrating-from-v1beta2-to-v1beta3" ] From 276da6543d16955e92a8edd5f46c5097b05fb386 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 21 May 2024 09:54:45 -0700 Subject: [PATCH 501/567] fix: create JWKS as part of adding handler instead of in verification step Signed-off-by: Ryan Hanchett --- .../implementations/auth/external/jwks.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index d734cbf984..ea62b3880c 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { jwtVerify, createRemoteJWKSet } from 'jose'; +import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose'; import { Config } from '@backstage/config'; import { TokenHandler } from './types'; @@ -30,6 +30,7 @@ export class JWKSHandler implements TokenHandler { issuers?: string[]; subjectPrefix?: string; url: URL; + jwks: JWTVerifyGetKey; }> = []; add(options: Config) { @@ -38,21 +39,28 @@ export class JWKSHandler implements TokenHandler { const audiences = options.getOptionalStringArray('audiences'); const subjectPrefix = options.getOptionalString('subjectPrefix'); const url = new URL(options.getString('url')); + const jwks = createRemoteJWKSet(url); if (!options.getString('url').match(/^\S+$/)) { throw new Error('Illegal URL, must be a set of non-space characters'); } - this.#entries.push({ algorithms, audiences, issuers, subjectPrefix, url }); + this.#entries.push({ + algorithms, + audiences, + issuers, + jwks, + subjectPrefix, + url, + }); } async verifyToken(token: string) { for (const entry of this.#entries) { try { - const jwks = createRemoteJWKSet(entry.url); const { payload: { sub }, - } = await jwtVerify(token, jwks, { + } = await jwtVerify(token, entry.jwks, { algorithms: entry.algorithms, issuer: entry.issuers, audience: entry.audiences, From 922bdddcfa935220271695bdab241503e58f8207 Mon Sep 17 00:00:00 2001 From: Ryan Hanchett Date: Tue, 21 May 2024 10:05:25 -0700 Subject: [PATCH 502/567] fix: add missing config values to config.d.ts Signed-off-by: Ryan Hanchett --- packages/backend-app-api/config.d.ts | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index f84493828a..5517af4f98 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -131,6 +131,47 @@ export interface Config { subject: string; }; } + | { + /** + * This access method consists of a JWKS endpoint that can be used to + * verify JWT tokens. + * + * Callers generate JWT tokens via 3rd party tooling + * and pass them in the Authorization header: + * + * ``` + * Authorization: Bearer eZv5o+fW3KnR3kVabMW4ZcDNLPl8nmMW + * ``` + */ + type: 'jwks'; + options: { + /** + * Sets the algorithms that should be used to verify the JWT tokens. + * The passed JWTs must have been signed using one of the listed algorithms. + */ + algorithms?: string[]; + /** + * Sets the issuers that should be used to verify the JWT tokens. + * Passed JWTs must have an `iss` claim which matches one of the specified issuers. + */ + issuers?: string[]; + /** + * Sets the audiences that should be used to verify the JWT tokens. + * The passed JWTs must have an "aud" claim that matches one of the audiences specified, + * or have no audience specified. + */ + audiences?: string[]; + /** + * Sets an optional subject prefix. Passes the subject to called plugins. + * Useful for debugging and tracking purposes. + */ + subjectPrefix?: string; + /** + * Sets the URL containing the JWKS endpoint. + */ + url: string; + }; + } >; }; }; From edafab4f3e033e1396e9c38870989f5ca80b5f3f Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 21 May 2024 19:21:14 +0200 Subject: [PATCH 503/567] keeps old docs and creates new docs with --new Signed-off-by: Peter Macdonald --- docs/permissions/getting-started--new.md | 36 ++++++ docs/permissions/getting-started.md | 146 +++++++++++++++++++++-- 2 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 docs/permissions/getting-started--new.md diff --git a/docs/permissions/getting-started--new.md b/docs/permissions/getting-started--new.md new file mode 100644 index 0000000000..b6198a222f --- /dev/null +++ b/docs/permissions/getting-started--new.md @@ -0,0 +1,36 @@ +--- +id: getting-started--new +title: Getting Started +description: How to get started with the permission framework as an integrator +--- + +Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. + +## Prerequisites + +The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy. + +### Upgrade to the latest version of Backstage + +To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! + +### Supply an identity resolver to populate group membership on sign in + +**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up. + +Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity. + +[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. + +## Enable and test the permissions system + +All you need to do now is enable the permissions system in your Backstage instance! + +1. Set the property `permission.enabled` to `true` in `app-config.yaml`. + +```yaml title="app-config.yaml" +permission: + enabled: true +``` + +Congratulations! Now that the framework is configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index 03290bd45e..4dabf3fcbd 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -4,6 +4,16 @@ title: Getting Started description: How to get started with the permission framework as an integrator --- +If you prefer to watch a video instead, you can start with this video introduction: + + + +:::note Note + +This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. + +::: + Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. ## Prerequisites @@ -12,7 +22,13 @@ The permissions framework depends on a few other Backstage systems, which must b ### Upgrade to the latest version of Backstage -To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! +The permissions framework itself is new to Backstage and still evolving quickly. To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! + +### Enable service-to-service authentication + +Service-to-service authentication allows Backstage backend code to verify that a given request originates from elsewhere in the Backstage backend. This is useful for tasks like collation of catalog entities in the search index. This type of request shouldn’t be permissioned, so it’s important to configure this feature before trying to use the permissions framework. + +To set up service-to-service authentication, follow the [service-to-service authentication docs](../auth/service-to-service-auth.md). ### Supply an identity resolver to populate group membership on sign in @@ -22,15 +38,129 @@ Like many other parts of Backstage, the permissions framework relies on informat [The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. -## Enable and test the permissions system +## Optionally add cookie-based authentication -All you need to do now is enable the permissions system in your Backstage instance! +Asset requests initiated by the browser will not include a token in the `Authorization` header. If these requests check authorization through the permission framework, as done in plugins like TechDocs, then you'll need to set up cookie-based authentication. Refer to the ["Authenticate API requests"](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial for a demonstration on how to implement this behavior. + +## Integrating the permission framework with your Backstage instance + +### 1. Set up the permission backend + +The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: + +1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend: + + ```bash + # From your Backstage root directory + yarn --cwd packages/backend add @backstage/plugin-permission-backend + ``` + +2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. + + ```typescript title="packages/backend/src/plugins/permission.ts" + import { createRouter } from '@backstage/plugin-permission-backend'; + import { + AuthorizeResult, + PolicyDecision, + } from '@backstage/plugin-permission-common'; + import { PermissionPolicy } from '@backstage/plugin-permission-node'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + class TestPermissionPolicy implements PermissionPolicy { + async handle(): Promise { + return { result: AuthorizeResult.ALLOW }; + } + } + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + config: env.config, + logger: env.logger, + discovery: env.discovery, + policy: new TestPermissionPolicy(), + identity: env.identity, + }); + } + ``` + +3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app: + + ```ts title="packages/backend/src/index.ts" + import proxy from './plugins/proxy'; + import techdocs from './plugins/techdocs'; + import search from './plugins/search'; + /* highlight-add-next-line */ + import permission from './plugins/permission'; + + async function main() { + const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const searchEnv = useHotMemoize(module, () => createEnv('search')); + const appEnv = useHotMemoize(module, () => createEnv('app')); + /* highlight-add-next-line */ + const permissionEnv = useHotMemoize(module, () => createEnv('permission')); + // .. + + apiRouter.use('/techdocs', await techdocs(techdocsEnv)); + apiRouter.use('/proxy', await proxy(proxyEnv)); + apiRouter.use('/search', await search(searchEnv)); + /* highlight-add-next-line */ + apiRouter.use('/permission', await permission(permissionEnv)); + // .. + } + ``` + +### 2. Enable and test the permissions system + +Now that the permission backend is running, it’s time to enable the permissions framework and make sure it’s working properly. 1. Set the property `permission.enabled` to `true` in `app-config.yaml`. -```yaml title="app-config.yaml" -permission: - enabled: true -``` + ```yaml title="app-config.yaml" + permission: + enabled: true + ``` -Congratulations! Now that the framework is configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! +2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: + + ```ts title="packages/backend/src/plugins/permission.ts" + import { createRouter } from '@backstage/plugin-permission-backend'; + import { + AuthorizeResult, + PolicyDecision, + } from '@backstage/plugin-permission-common'; + /* highlight-remove-next-line */ + import { PermissionPolicy } from '@backstage/plugin-permission-node'; + /* highlight-add-start */ + import { + PermissionPolicy, + PolicyQuery, + } from '@backstage/plugin-permission-node'; + /* highlight-add-end */ + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + class TestPermissionPolicy implements PermissionPolicy { + /* highlight-remove-next-line */ + async handle(): Promise { + /* highlight-add-start */ + async handle(request: PolicyQuery): Promise { + if (request.permission.name === 'catalog.entity.delete') { + return { + result: AuthorizeResult.DENY, + }; + } + /* highlight-add-end */ + + return { result: AuthorizeResult.ALLOW }; + } + } + ``` + +3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. + +![Entity detail page showing disabled unregister entity context menu entry](../assets/permissions/disabled-unregister-entity.png) + +Now that the framework is fully configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! From 245883038b232cb0503d15677da6d4475deaf149 Mon Sep 17 00:00:00 2001 From: Mukul Ambulgekar Date: Tue, 21 May 2024 12:42:46 -0700 Subject: [PATCH 504/567] Fix Typo in ReviewStep Label Signed-off-by: Mukul Ambulgekar --- .../scaffolder-react/src/next/components/Stepper/Stepper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 5fbda65957..5bfdf1a44a 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -214,7 +214,7 @@ export const Stepper = (stepperProps: StepperProps) => { ); })} - ${reviewLabel} + {reviewLabel}
    From 928cfa0b6c73469ce7ffc6d883d7e6d5b8bd03f5 Mon Sep 17 00:00:00 2001 From: Mukul Ambulgekar Date: Tue, 21 May 2024 12:55:03 -0700 Subject: [PATCH 505/567] Updated changeset Signed-off-by: Mukul Ambulgekar --- .changeset/tall-pumas-teach.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tall-pumas-teach.md diff --git a/.changeset/tall-pumas-teach.md b/.changeset/tall-pumas-teach.md new file mode 100644 index 0000000000..39e7c06297 --- /dev/null +++ b/.changeset/tall-pumas-teach.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Fixed a typo '$' in the review step label From a7810510fc986e874f3bb0a68e405057e4ef6e0c Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Wed, 22 May 2024 10:13:30 +0200 Subject: [PATCH 506/567] fix: handle missing orgEnabled config key gracefully by logging and returning early. Fixes #24857 Signed-off-by: ElaineDeMattosSilvaB --- .../GitlabOrgDiscoveryEntityProvider.test.ts | 14 +++++++------- .../providers/GitlabOrgDiscoveryEntityProvider.ts | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts index dfbf39267f..5a60f1b4b3 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -75,16 +75,16 @@ describe('GitlabOrgDiscoveryEntityProvider - configuration', () => { }).toThrow('No gitlab integration found that matches host example.com'); }); - it('should throw error when org configuration not found', () => { + it('should log a message and return when org configuration not found', () => { const schedule = new PersistingTaskRunner(); const config = new ConfigReader(mock.config_no_org_integration); - expect(() => { - GitlabOrgDiscoveryEntityProvider.fromConfig(config, { - logger, - schedule, - }); - }).toThrow('Org not enabled for test-id'); + GitlabOrgDiscoveryEntityProvider.fromConfig(config, { + logger, + schedule, + }); + + expect(logger.info).toHaveBeenCalledWith('Org not enabled for test-id.'); }); it('should throw error when saas without group configuration', () => { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index 0135c7b4a9..e10f9ee313 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, @@ -28,7 +29,6 @@ import { import { EventsService } from '@backstage/plugin-events-node'; import { merge } from 'lodash'; import * as uuid from 'uuid'; -import { LoggerService } from '@backstage/backend-plugin-api'; import { GitLabClient, @@ -133,7 +133,8 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { const integration = integrations.byHost(providerConfig.host); if (!providerConfig.orgEnabled) { - throw new Error(`Org not enabled for ${providerConfig.id}.`); + options.logger.info(`Org not enabled for ${providerConfig.id}.`); + return; } if (!integration) { From 150fc77dda093682db1473b895a6e2ac52ac5ec0 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Wed, 22 May 2024 10:18:00 +0200 Subject: [PATCH 507/567] chore: add changeset Signed-off-by: ElaineDeMattosSilvaB --- .changeset/nine-hairs-kick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nine-hairs-kick.md diff --git a/.changeset/nine-hairs-kick.md b/.changeset/nine-hairs-kick.md new file mode 100644 index 0000000000..9fac4bb4c2 --- /dev/null +++ b/.changeset/nine-hairs-kick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Fixed an issue in `GitlabOrgDiscoveryEntityProvider` where a missing `orgEnabled`config key was throwing an error instead of just returning. From 4ad64d160a4ce77e3e5f01a15a73b0a1d41e2e09 Mon Sep 17 00:00:00 2001 From: Marc Palm <17670840+marcpalm@users.noreply.github.com> Date: Wed, 22 May 2024 10:34:36 +0200 Subject: [PATCH 508/567] fix: remove async imports https://github.com/backstage/backstage/issues/24864 Signed-off-by: Marc Palm <17670840+marcpalm@users.noreply.github.com> Signed-off-by: Marc Palm --- packages/cli/src/lib/bundler/server.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 53f3b2c25a..29298dcc5f 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -165,12 +165,12 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be }); if (process.env.EXPERIMENTAL_VITE) { - const vite = await import('vite'); - const { default: viteReact } = await import('@vitejs/plugin-react'); - const { nodePolyfills: viteNodePolyfills } = await import( + const vite = require('vite'); + const { default: viteReact } = require('@vitejs/plugin-react'); + const { nodePolyfills: viteNodePolyfills } = require( 'vite-plugin-node-polyfills' ); - const { createHtmlPlugin: viteHtml } = await import('vite-plugin-html'); + const { createHtmlPlugin: viteHtml } = require('vite-plugin-html'); viteServer = await vite.createServer({ define: { global: 'window', From 111f7cf05d906dae1835b87c6057981e152d37f2 Mon Sep 17 00:00:00 2001 From: Marc Palm Date: Wed, 22 May 2024 10:45:24 +0200 Subject: [PATCH 509/567] fix: prettier Signed-off-by: Marc Palm --- packages/cli/src/lib/bundler/server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 29298dcc5f..1da15cd37e 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -167,9 +167,9 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be if (process.env.EXPERIMENTAL_VITE) { const vite = require('vite'); const { default: viteReact } = require('@vitejs/plugin-react'); - const { nodePolyfills: viteNodePolyfills } = require( - 'vite-plugin-node-polyfills' - ); + const { + nodePolyfills: viteNodePolyfills, + } = require('vite-plugin-node-polyfills'); const { createHtmlPlugin: viteHtml } = require('vite-plugin-html'); viteServer = await vite.createServer({ define: { From bca953dbb6ad702d96fab94443bc90a1ed25521e Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 22 May 2024 11:17:05 +0200 Subject: [PATCH 510/567] Update .changeset/nine-hairs-kick.md Signed-off-by: Vincenzo Scamporlino --- .changeset/nine-hairs-kick.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/nine-hairs-kick.md b/.changeset/nine-hairs-kick.md index 9fac4bb4c2..e4160640b4 100644 --- a/.changeset/nine-hairs-kick.md +++ b/.changeset/nine-hairs-kick.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend-module-gitlab': patch --- -Fixed an issue in `GitlabOrgDiscoveryEntityProvider` where a missing `orgEnabled`config key was throwing an error instead of just returning. +Fixed an issue in `GitlabOrgDiscoveryEntityProvider` where a missing `orgEnabled` config key was throwing an error. From e187e99b4702d7acf02dad9e84cca217b1b7982c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 22 May 2024 15:21:24 +0200 Subject: [PATCH 511/567] port proxy tests to msw2 to try to get rid of spurious build errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/proxy-backend/package.json | 2 +- .../src/service/router.config.test.ts | 15 ++++++--------- .../src/service/router.credentials.test.ts | 15 +++++++-------- yarn.lock | 2 +- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index d92dba9069..1ca6cbe5f5 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -74,7 +74,7 @@ "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", - "msw": "^1.0.0", + "msw": "^2.0.0", "node-fetch": "^2.6.7", "portfinder": "^1.0.32", "supertest": "^6.1.3" diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index 0de4787658..20aed5452a 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -24,7 +24,7 @@ import { StaticConfigSource, } from '@backstage/config-loader'; import express from 'express'; -import { rest } from 'msw'; +import { http, HttpResponse } from 'msw'; import { setupServer } from 'msw/node'; import request from 'supertest'; import { createRouter } from './router'; @@ -35,14 +35,11 @@ import { mockServices } from '@backstage/backend-test-utils'; describe('createRouter reloadable configuration', () => { const server = setupServer( - rest.get('https://non-existing-example.com/', (req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - url: req.url.toString(), - headers: req.headers.all(), - }), - ), + http.get('https://non-existing-example.com/', req => + HttpResponse.json({ + url: req.request.url.toString(), + headers: req.request.headers, + }), ), ); diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 1496b6ffb6..85fad9f038 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/backend-test-utils'; import { ResponseError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; -import { rest } from 'msw'; +import { http, HttpResponse, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'node-fetch'; import portFinder from 'portfinder'; @@ -82,13 +82,12 @@ describe('credentials', () => { }; worker.use( - rest.all(`${baseUrl}/*`, req => req.passthrough()), - rest.get('http://target.com/*', (req, res, ctx) => { - const auth = req.headers.get('authorization'); - return res( - ctx.status(200), - ctx.json({ payload: { forwardedAuthorization: auth ?? false } }), - ); + http.all(`${baseUrl}/*`, () => passthrough()), + http.get('http://target.com/*', req => { + const auth = req.request.headers.get('authorization'); + return HttpResponse.json({ + payload: { forwardedAuthorization: auth ?? false }, + }); }), ); diff --git a/yarn.lock b/yarn.lock index d006daa23c..a823457dce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6471,7 +6471,7 @@ __metadata: express-promise-router: ^4.1.0 http-proxy-middleware: ^2.0.0 morgan: ^1.10.0 - msw: ^1.0.0 + msw: ^2.0.0 node-fetch: ^2.6.7 portfinder: ^1.0.32 supertest: ^6.1.3 From 02103becc6849f77ff08618d3c551797e0df7ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 21 May 2024 12:39:27 +0200 Subject: [PATCH 512/567] move over cache and database services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/six-llamas-give.md | 6 + packages/backend-app-api/api-report.md | 4 +- packages/backend-common/api-report.md | 47 ++- .../src/cache/CacheManager.test.ts | 384 ------------------ .../src/cache/cacheToPluginCacheManager.ts | 34 ++ packages/backend-common/src/cache/index.ts | 11 +- packages/backend-common/src/cache/reexport.ts | 29 ++ packages/backend-common/src/cache/types.ts | 37 +- packages/backend-common/src/database/index.ts | 7 +- .../backend-common/src/database/reexport.ts | 37 ++ .../src/discovery/HostDiscovery.ts | 9 +- .../backend-common/src/discovery/index.ts | 1 + packages/backend-defaults/api-report-cache.md | 33 +- .../backend-defaults/api-report-database.md | 37 ++ packages/backend-defaults/package.json | 13 +- .../entrypoints}/cache/CacheClient.test.ts | 0 .../src/entrypoints}/cache/CacheClient.ts | 0 .../cache/CacheManager.integration.test.ts | 39 +- .../src/entrypoints}/cache/CacheManager.ts | 63 +-- .../entrypoints/cache/cacheServiceFactory.ts | 7 +- .../src/entrypoints/cache/index.ts | 2 + .../src/entrypoints/cache/types.ts | 46 +++ .../database/DatabaseManager.test.ts | 0 .../entrypoints}/database/DatabaseManager.ts | 2 +- .../connectors/defaultNameOverride.test.ts | 0 .../connectors/defaultNameOverride.ts | 0 .../connectors/defaultSchemaOverride.test.ts | 0 .../connectors/defaultSchemaOverride.ts | 0 .../entrypoints}/database/connectors/index.ts | 0 .../connectors/mergeDatabaseConfig.test.ts | 0 .../connectors/mergeDatabaseConfig.ts | 0 .../database/connectors/mysql.test.ts | 0 .../entrypoints}/database/connectors/mysql.ts | 0 .../database/connectors/postgres.test.ts | 0 .../database/connectors/postgres.ts | 0 .../database/connectors/sqlite3.test.ts | 0 .../database/connectors/sqlite3.ts | 0 .../src/entrypoints/database/index.ts | 6 + .../src/entrypoints/database/types.ts | 100 +++++ plugins/auth-backend/api-report.md | 4 +- yarn.lock | 11 + 41 files changed, 469 insertions(+), 500 deletions(-) create mode 100644 .changeset/six-llamas-give.md delete mode 100644 packages/backend-common/src/cache/CacheManager.test.ts create mode 100644 packages/backend-common/src/cache/cacheToPluginCacheManager.ts create mode 100644 packages/backend-common/src/cache/reexport.ts create mode 100644 packages/backend-common/src/database/reexport.ts rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheClient.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheClient.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheManager.integration.test.ts (76%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/cache/CacheManager.ts (82%) create mode 100644 packages/backend-defaults/src/entrypoints/cache/types.ts rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/DatabaseManager.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/DatabaseManager.ts (99%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultNameOverride.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultNameOverride.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultSchemaOverride.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/defaultSchemaOverride.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/index.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mergeDatabaseConfig.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mergeDatabaseConfig.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mysql.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/mysql.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/postgres.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/postgres.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/sqlite3.test.ts (100%) rename packages/{backend-common/src => backend-defaults/src/entrypoints}/database/connectors/sqlite3.ts (100%) create mode 100644 packages/backend-defaults/src/entrypoints/database/types.ts diff --git a/.changeset/six-llamas-give.md b/.changeset/six-llamas-give.md new file mode 100644 index 0000000000..9bfbf52625 --- /dev/null +++ b/.changeset/six-llamas-give.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-defaults': minor +'@backstage/backend-common': minor +--- + +Deprecated and moved over core services to `@backstage/backend-defaults` diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 49965f4c46..11d9dd90d4 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -8,7 +8,7 @@ import type { AppConfig } from '@backstage/config'; import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ConfigSchema } from '@backstage/config-loader'; import { CorsOptions } from 'cors'; @@ -66,7 +66,7 @@ export interface Backend { } // @public @deprecated (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: () => ServiceFactory; // @public (undocumented) export function createConfigSecretEnumerator(options: { diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 4e92420667..7e677bd570 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -17,11 +17,12 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { BitbucketCloudIntegration } from '@backstage/integration'; import { BitbucketIntegration } from '@backstage/integration'; import { BitbucketServerIntegration } from '@backstage/integration'; -import { CacheService as CacheClient } from '@backstage/backend-plugin-api'; -import { CacheServiceOptions as CacheClientOptions } from '@backstage/backend-plugin-api'; -import { CacheServiceSetOptions as CacheClientSetOptions } from '@backstage/backend-plugin-api'; +import { CacheService } from '@backstage/backend-plugin-api'; +import { CacheServiceOptions } from '@backstage/backend-plugin-api'; +import type { CacheServiceSetOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import cors from 'cors'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; import Docker from 'dockerode'; import { ErrorRequestHandler } from 'express'; import express from 'express'; @@ -44,7 +45,6 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { MergeResult } from 'isomorphic-git'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; -import { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; import { Readable } from 'stream'; @@ -193,18 +193,26 @@ export class BitbucketUrlReader implements UrlReader { toString(): string; } -export { CacheClient }; +// @public @deprecated (undocumented) +export type CacheClient = CacheService; -export { CacheClientOptions }; +// @public @deprecated (undocumented) +export type CacheClientOptions = CacheServiceOptions; -export { CacheClientSetOptions }; +// @public @deprecated (undocumented) +export type CacheClientSetOptions = CacheServiceSetOptions; // @public export class CacheManager { - forPlugin(pluginId: string): PluginCacheManager; + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + }; static fromConfig( config: Config, - options?: CacheManagerOptions, + options?: { + logger?: LoggerService; + onError?: (err: Error) => void; + }, ): CacheManager; } @@ -214,10 +222,10 @@ export type CacheManagerOptions = { onError?: (err: Error) => void; }; -// @public (undocumented) -export function cacheToPluginCacheManager( - cache: CacheClient, -): PluginCacheManager; +// @public +export function cacheToPluginCacheManager(cache: CacheService): { + getClient(options?: CacheServiceOptions): CacheService; +}; // @public @deprecated export const coloredFormat: winston.Logform.Format; @@ -575,10 +583,10 @@ export const legacyPlugin: ( default: LegacyCreateRouter< TransformedEnv< { - cache: CacheClient; + cache: CacheService; config: RootConfigService; database: PluginDatabaseManager; - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; logger: LoggerService; permissions: PermissionsService; scheduler: SchedulerService; @@ -588,7 +596,9 @@ export const legacyPlugin: ( }, { logger: (log: LoggerService) => Logger; - cache: (cache: CacheClient) => PluginCacheManager; + cache: (cache: CacheService) => { + getClient(options?: CacheServiceOptions | undefined): CacheService; + }; } > >; @@ -639,12 +649,13 @@ export function notFoundHandler(): RequestHandler; // @public (undocumented) export interface PluginCacheManager { // (undocumented) - getClient(options?: CacheClientOptions): CacheClient; + getClient(options?: CacheServiceOptions): CacheService; } export { PluginDatabaseManager }; -export { PluginEndpointDiscovery }; +// @public @deprecated (undocumented) +export type PluginEndpointDiscovery = DiscoveryService; // @public export interface PullOptions { diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts deleted file mode 100644 index 4b7bb05efd..0000000000 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConfigReader } from '@backstage/config'; -import Keyv from 'keyv'; -import KeyvMemcache from '@keyv/memcache'; -import KeyvRedis from '@keyv/redis'; -import { DefaultCacheClient } from './CacheClient'; -import { CacheManager } from './CacheManager'; - -jest.createMockFromModule('keyv'); -jest.mock('keyv'); -jest.createMockFromModule('@keyv/memcache'); -jest.mock('@keyv/memcache'); -jest.createMockFromModule('@keyv/redis'); -jest.mock('@keyv/redis'); -jest.mock('./CacheClient', () => { - return { - DefaultCacheClient: jest.fn(), - }; -}); - -const globalDefaultTtl = 1234; -describe('CacheManager', () => { - const defaultConfigOptions = { - backend: { - cache: { - store: 'memory', - defaultTtl: globalDefaultTtl, - }, - }, - }; - const defaultConfig = () => new ConfigReader(defaultConfigOptions); - - afterEach(() => jest.resetAllMocks()); - - describe('CacheManager.fromConfig', () => { - it('accesses the backend.cache key', () => { - const getOptionalString = jest.fn(); - const getOptionalBoolean = jest.fn(); - const getOptionalNumber = jest.fn(); - const config = defaultConfig(); - config.getOptionalString = getOptionalString; - config.getOptionalBoolean = getOptionalBoolean; - config.getOptionalNumber = getOptionalNumber; - - CacheManager.fromConfig(config); - - expect(getOptionalString.mock.calls[0][0]).toEqual('backend.cache.store'); - expect(getOptionalString.mock.calls[1][0]).toEqual( - 'backend.cache.connection', - ); - expect(getOptionalBoolean.mock.calls[0][0]).toEqual( - 'backend.cache.useRedisSets', - ); - expect(getOptionalNumber.mock.calls[0][0]).toEqual( - 'backend.cache.defaultTtl', - ); - }); - - it('does not require the backend.cache key', () => { - const config = new ConfigReader({ backend: {} }); - expect(() => { - CacheManager.fromConfig(config); - }).not.toThrow(); - }); - - it('throws on unknown cache store', () => { - const config = new ConfigReader({ - backend: { cache: { store: 'notreal' } }, - }); - expect(() => { - CacheManager.fromConfig(config); - }).toThrow(); - }); - }); - - describe('CacheManager.forPlugin', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - - it('connects to a cache store scoped to the plugin', async () => { - const pluginId = 'test1'; - manager.forPlugin(pluginId).getClient(); - - const client = DefaultCacheClient as jest.Mock; - expect(client).toHaveBeenCalledTimes(1); - }); - - it('attaches error handler to client', () => { - const pluginId = 'error-test'; - manager.forPlugin(pluginId).getClient(); - - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - expect(realClient.on).toHaveBeenCalledWith('error', expect.any(Function)); - }); - - it('provides different plugins different cache clients', async () => { - const plugin1Id = 'test1'; - const plugin2Id = 'test2'; - const expectedTtl = 3600; - manager.forPlugin(plugin1Id).getClient({ defaultTtl: expectedTtl }); - manager.forPlugin(plugin2Id).getClient({ defaultTtl: expectedTtl }); - - const client = DefaultCacheClient as jest.Mock; - const cache = Keyv as unknown as jest.Mock; - expect(cache).toHaveBeenCalledTimes(2); - expect(client).toHaveBeenCalledTimes(2); - - const plugin1CallArgs = cache.mock.calls[0]; - const plugin2CallArgs = cache.mock.calls[1]; - expect(plugin1CallArgs[0].namespace).not.toEqual( - plugin2CallArgs[0].namespace, - ); - }); - }); - - describe('CacheManager.forPlugin stores', () => { - it('returns memory client when no cache is configured', () => { - const manager = CacheManager.fromConfig( - new ConfigReader({ backend: {} }), - ); - const expectedTtl = 3600; - const expectedNamespace = 'test-plugin'; - manager - .forPlugin(expectedNamespace) - .getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: expectedTtl, - namespace: expectedNamespace, - }); - }); - - it('returns memory client when explicitly configured', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const expectedTtl = 3600; - const expectedNamespace = 'test-plugin'; - manager - .forPlugin(expectedNamespace) - .getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: expectedTtl, - namespace: expectedNamespace, - }); - }); - - it('returns memory client with a global defaultTtl when explicitly configured', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const expectedNamespace = 'test-plugin'; - manager.forPlugin(expectedNamespace).getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCalls = cache.mock.calls.splice(-1); - const callArgs = mockCalls[0]; - expect(callArgs[0]).toMatchObject({ - ttl: globalDefaultTtl, - namespace: expectedNamespace, - }); - }); - - it('shares memory across multiple instances of the memory client', () => { - const manager = CacheManager.fromConfig(defaultConfig()); - const plugin = 'test-plugin'; - - // Instantiate two in-memory clients. - manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); - manager.forPlugin(plugin).getClient({ defaultTtl: 10 }); - - const cache = Keyv as unknown as jest.Mock; - const mockCall2 = cache.mock.calls.splice(-1)[0][0]; - const mockCall1 = cache.mock.calls.splice(-1)[0][0]; - - // Note: .toBe() checks referential identity of object instances. - expect(mockCall1.store).toBe(mockCall2.store); - }); - - it('returns a memcache client when configured', () => { - const expectedHost = '127.0.0.1:11211'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'memcache', - connection: expectedHost, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache); - const memcache = KeyvMemcache as unknown as jest.Mock; - const mockMemcacheCalls = memcache.mock.calls.splice(-1); - expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); - }); - - it('returns a memcache client with a global defaultTtl when configured', () => { - const expectedHost = '127.0.0.1:11211'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'memcache', - connection: expectedHost, - defaultTtl: globalDefaultTtl, - }, - }, - }), - ); - manager.forPlugin('test').getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: globalDefaultTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache); - const memcache = KeyvMemcache as unknown as jest.Mock; - const mockMemcacheCalls = memcache.mock.calls.splice(-1); - expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); - }); - - it('returns a Redis client when configured', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - - it('returns a Redis client with a global defaultTtl when configured', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - defaultTtl: globalDefaultTtl, - }, - }, - }), - ); - manager.forPlugin('test').getClient(); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: globalDefaultTtl, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - - it('returns a Redis client when configured with useRedisSets flag', () => { - const redisConnection = 'redis://127.0.0.1:6379'; - const useRedisSets = false; - const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection: redisConnection, - useRedisSets: useRedisSets, - }, - }, - }), - ); - const expectedTtl = 3600; - manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); - - const cache = Keyv as unknown as jest.Mock; - const mockCacheCalls = cache.mock.calls.splice(-1); - expect(mockCacheCalls[0][0]).toMatchObject({ - ttl: expectedTtl, - useRedisSets: useRedisSets, - }); - expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); - const redis = KeyvRedis as unknown as jest.Mock; - const mockRedisCalls = redis.mock.calls.splice(-1); - expect(mockRedisCalls[0][0]).toEqual(redisConnection); - }); - }); - - describe('connection errors', () => { - it('uses provided logger', () => { - // Set up and inject mock logger. - const mockLogger = { child: jest.fn(), error: jest.fn() }; - mockLogger.child.mockImplementation(() => mockLogger as any); - const manager = CacheManager.fromConfig(defaultConfig(), { - logger: mockLogger as any, - }); - - // Set up a cache client using the configured manager. - manager.forPlugin('error-logger-test').getClient(); - - // Retrieve the error handler attached to the cache client. - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - const realOnError = realClient.on as jest.Mock; - const realHandler = realOnError.mock.calls.splice(-1)[0][1]; - - // Invoke the actual error handler. - const expectedError = new Error('some error'); - realHandler(expectedError); - expect(mockLogger.error).toHaveBeenCalledWith( - 'Failed to create cache client', - expectedError, - ); - }); - - it('calls provided handler', () => { - // Set up and inject mock logger. - const mockHandler = jest.fn(); - const manager = CacheManager.fromConfig(defaultConfig(), { - onError: mockHandler, - }); - - // Set up a cache client using the configured manager. - manager.forPlugin('error-handler-test').getClient(); - - // Retrieve the error handler attached to the cache client. - const client = DefaultCacheClient as jest.Mock; - const mockCalls = client.mock.calls.splice(-1); - const realClient = mockCalls[0][0] as Keyv; - const realOnError = realClient.on as jest.Mock; - const realHandler = realOnError.mock.calls.splice(-1)[0][1]; - - // Invoke the actual error handler. - const expectedError = new Error('some error'); - realHandler(expectedError); - expect(mockHandler).toHaveBeenCalledWith(expectedError); - }); - }); -}); diff --git a/packages/backend-common/src/cache/cacheToPluginCacheManager.ts b/packages/backend-common/src/cache/cacheToPluginCacheManager.ts new file mode 100644 index 0000000000..2654934804 --- /dev/null +++ b/packages/backend-common/src/cache/cacheToPluginCacheManager.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + CacheService, + CacheServiceOptions, +} from '@backstage/backend-plugin-api'; + +/** + * Compatibility wrapper for going from a new-backend cache service to the + * old-backend plugin cache manager. + * + * @public + */ +export function cacheToPluginCacheManager(cache: CacheService): { + getClient(options?: CacheServiceOptions): CacheService; +} { + return { + getClient: (opts: CacheServiceOptions) => cache.withOptions(opts), + }; +} diff --git a/packages/backend-common/src/cache/index.ts b/packages/backend-common/src/cache/index.ts index 0187aa45d5..9d05745ebd 100644 --- a/packages/backend-common/src/cache/index.ts +++ b/packages/backend-common/src/cache/index.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -export { CacheManager, cacheToPluginCacheManager } from './CacheManager'; -export type { - CacheClient, - CacheClientSetOptions, - PluginCacheManager, - CacheManagerOptions, - CacheClientOptions, -} from './types'; +export { cacheToPluginCacheManager } from './cacheToPluginCacheManager'; +export * from './reexport'; +export * from './types'; diff --git a/packages/backend-common/src/cache/reexport.ts b/packages/backend-common/src/cache/reexport.ts new file mode 100644 index 0000000000..9f6106de60 --- /dev/null +++ b/packages/backend-common/src/cache/reexport.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/* + * NOTE(freben): This is a temporary hack. We use cross-package imports so that + * we do not have to maintain double implementations for the time being, until + * backend-common is properly removed. + */ + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +export { CacheManager } from '../../../backend-defaults/src/entrypoints/cache/CacheManager'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +export { + type PluginCacheManager, + type CacheManagerOptions, +} from '../../../backend-defaults/src/entrypoints/cache/types'; diff --git a/packages/backend-common/src/cache/types.ts b/packages/backend-common/src/cache/types.ts index 93fa0ac77c..83b6916843 100644 --- a/packages/backend-common/src/cache/types.ts +++ b/packages/backend-common/src/cache/types.ts @@ -14,39 +14,26 @@ * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { +import type { CacheService, + CacheServiceSetOptions, CacheServiceOptions, } from '@backstage/backend-plugin-api'; -export type { - CacheService as CacheClient, - CacheServiceSetOptions as CacheClientSetOptions, - CacheServiceOptions as CacheClientOptions, -} from '@backstage/backend-plugin-api'; - /** - * Options given when constructing a {@link CacheManager}. - * * @public + * @deprecated Use `CacheService` from the `@backstage/backend-plugin-api` package instead */ -export type CacheManagerOptions = { - /** - * An optional logger for use by the PluginCacheManager. - */ - logger?: LoggerService; - - /** - * An optional handler for connection errors emitted from the underlying data - * store. - */ - onError?: (err: Error) => void; -}; +export type CacheClient = CacheService; /** * @public + * @deprecated Use `CacheServiceSetOptions` from the `@backstage/backend-plugin-api` package instead */ -export interface PluginCacheManager { - getClient(options?: CacheServiceOptions): CacheService; -} +export type CacheClientSetOptions = CacheServiceSetOptions; + +/** + * @public + * @deprecated Use `CacheServiceOptions` from the `@backstage/backend-plugin-api` package instead + */ +export type CacheClientOptions = CacheServiceOptions; diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index 77d75653b8..0e95261d82 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -14,10 +14,5 @@ * limitations under the License. */ -export { DatabaseManager, dropDatabase } from './DatabaseManager'; -export type { - DatabaseManagerOptions, - LegacyRootDatabaseService, -} from './DatabaseManager'; - +export * from './reexport'; export type { PluginDatabaseManager } from './types'; diff --git a/packages/backend-common/src/database/reexport.ts b/packages/backend-common/src/database/reexport.ts new file mode 100644 index 0000000000..1ecff9be15 --- /dev/null +++ b/packages/backend-common/src/database/reexport.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * NOTE(freben): This is a temporary hack. We use cross-package imports so that + * we do not have to maintain double implementations for the time being, until + * backend-common is properly removed. When it is, the impleemntation should be + * moved into this part of the repo instead. + */ + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { + DatabaseManager, + dropDatabase, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, +} from '../../../backend-defaults/src/entrypoints/database/DatabaseManager'; + +export { + DatabaseManager, + dropDatabase, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, +}; diff --git a/packages/backend-common/src/discovery/HostDiscovery.ts b/packages/backend-common/src/discovery/HostDiscovery.ts index cf0ddff611..77810b7f6d 100644 --- a/packages/backend-common/src/discovery/HostDiscovery.ts +++ b/packages/backend-common/src/discovery/HostDiscovery.ts @@ -15,8 +15,13 @@ */ import { HostDiscovery as _HostDiscovery } from '@backstage/backend-app-api'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; -export type { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api'; +/** + * @public + * @deprecated Use `DiscoveryService` from `@backstage/backend-plugin-api` instead + */ +export type PluginEndpointDiscovery = DiscoveryService; /** * HostDiscovery is a basic PluginEndpointDiscovery implementation @@ -40,6 +45,6 @@ export const HostDiscovery = _HostDiscovery; * resolved to the same host, so there won't be any balancing of internal traffic. * * @public - * @deprecated Use {@link HostDiscovery} instead + * @deprecated Use `HostDiscovery` from `@backstage/backend-defaults/discovery` instead */ export const SingleHostDiscovery = _HostDiscovery; diff --git a/packages/backend-common/src/discovery/index.ts b/packages/backend-common/src/discovery/index.ts index bad721d4da..827fd059ad 100644 --- a/packages/backend-common/src/discovery/index.ts +++ b/packages/backend-common/src/discovery/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { HostDiscovery, SingleHostDiscovery, diff --git a/packages/backend-defaults/api-report-cache.md b/packages/backend-defaults/api-report-cache.md index 150ed391f0..2cd2f3efc9 100644 --- a/packages/backend-defaults/api-report-cache.md +++ b/packages/backend-defaults/api-report-cache.md @@ -3,11 +3,40 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; +import { CacheServiceOptions } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +// @public +export class CacheManager { + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + }; + static fromConfig( + config: Config, + options?: { + logger?: LoggerService; + onError?: (err: Error) => void; + }, + ): CacheManager; +} + +// @public +export type CacheManagerOptions = { + logger?: LoggerService; + onError?: (err: Error) => void; +}; + // @public (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: () => ServiceFactory; + +// @public (undocumented) +export interface PluginCacheManager { + // (undocumented) + getClient(options?: CacheServiceOptions): CacheService; +} // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-database.md b/packages/backend-defaults/api-report-database.md index 512e1febc9..0edadaad59 100644 --- a/packages/backend-defaults/api-report-database.md +++ b/packages/backend-defaults/api-report-database.md @@ -3,14 +3,51 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { Config } from '@backstage/config'; +import { DatabaseService } from '@backstage/backend-plugin-api'; +import { LifecycleService } from '@backstage/backend-plugin-api'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; +import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +// @public +export class DatabaseManager implements LegacyRootDatabaseService { + forPlugin( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): DatabaseService; + static fromConfig( + config: Config, + options?: DatabaseManagerOptions, + ): DatabaseManager; +} + +// @public +export type DatabaseManagerOptions = { + migrations?: DatabaseService['migrations']; + logger?: LoggerService; +}; + // @public (undocumented) export const databaseServiceFactory: () => ServiceFactory< PluginDatabaseManager, 'plugin' >; +// @public +export function dropDatabase( + dbConfig: Config, + ...databaseNames: string[] +): Promise; + +// @public +export type LegacyRootDatabaseService = { + forPlugin(pluginId: string): DatabaseService; +}; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index e4db289e33..f1eb7e0718 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", - "description": "Backend defaults used by Backstage backend apps", "version": "0.2.19-next.0", + "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" }, @@ -84,6 +84,7 @@ "dependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", + "@backstage/backend-dev-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", @@ -91,12 +92,22 @@ "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", + "@keyv/memcache": "^1.3.5", + "@keyv/redis": "^2.5.3", "@opentelemetry/api": "^1.3.0", + "better-sqlite3": "^9.0.0", "cron": "^3.0.0", + "fs-extra": "^11.2.0", + "keyv": "^4.5.2", "knex": "^3.0.0", "lodash": "^4.17.21", "luxon": "^3.0.0", + "mysql2": "^3.0.0", + "p-limit": "^3.1.0", + "pg": "^8.11.3", + "pg-connection-string": "^2.3.0", "uuid": "^9.0.0", + "yn": "^4.0.0", "zod": "^3.22.4" }, "devDependencies": { diff --git a/packages/backend-common/src/cache/CacheClient.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheClient.test.ts similarity index 100% rename from packages/backend-common/src/cache/CacheClient.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheClient.test.ts diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-defaults/src/entrypoints/cache/CacheClient.ts similarity index 100% rename from packages/backend-common/src/cache/CacheClient.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheClient.ts diff --git a/packages/backend-common/src/cache/CacheManager.integration.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts similarity index 76% rename from packages/backend-common/src/cache/CacheManager.integration.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts index 7dc60a9051..a10b02a62e 100644 --- a/packages/backend-common/src/cache/CacheManager.integration.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; -import { CacheManager } from './CacheManager'; +import { mockServices } from '@backstage/backend-test-utils'; import KeyvRedis from '@keyv/redis'; +import { CacheManager } from './CacheManager'; // This test is in a separate file because the main test file uses other mocking // that might interfere with this one. @@ -24,24 +24,27 @@ import KeyvRedis from '@keyv/redis'; // Contrived code because it's hard to spy on a default export jest.mock('@keyv/redis', () => { const ActualKeyvRedis = jest.requireActual('@keyv/redis'); - return jest - .fn() - .mockImplementation((...args: any[]) => new ActualKeyvRedis(...args)); + return jest.fn((...args: any[]) => { + return new ActualKeyvRedis(...args); + }); }); describe('CacheManager integration', () => { describe('redis', () => { it('only creates one underlying connection', async () => { + const connection = + process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; + if (!connection) { + return; + } + const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - // no actual connection errors will be seen since we don't interact with it - connection: 'redis://localhost:6379', - }, + mockServices.rootConfig({ + data: { + backend: { cache: { store: 'redis', connection } }, }, }), + { onError: e => expect(e).not.toBeDefined() }, ); manager.forPlugin('p1').getClient(); @@ -56,20 +59,18 @@ describe('CacheManager integration', () => { // TODO(freben): This could be frameworkified as TestCaches just like // TestDatabases, but that will have to come some other day const connection = - process.env.BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING; + process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; if (!connection) { return; } const manager = CacheManager.fromConfig( - new ConfigReader({ - backend: { - cache: { - store: 'redis', - connection, - }, + mockServices.rootConfig({ + data: { + backend: { cache: { store: 'redis', connection } }, }, }), + { onError: e => expect(e).not.toBeDefined() }, ); const plugin1 = manager.forPlugin('p1').getClient(); diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts similarity index 82% rename from packages/backend-common/src/cache/CacheManager.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.ts index 8af742be8c..70f952ea46 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts @@ -14,21 +14,25 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import Keyv from 'keyv'; -import KeyvMemcache from '@keyv/memcache'; -import KeyvRedis from '@keyv/redis'; import { CacheService, CacheServiceOptions, LoggerService, } from '@backstage/backend-plugin-api'; -import { getRootLogger } from '../logging'; +import { Config } from '@backstage/config'; +import Keyv from 'keyv'; import { DefaultCacheClient } from './CacheClient'; -import { CacheManagerOptions, PluginCacheManager } from './types'; +import { CacheManagerOptions } from './types'; type StoreFactory = (pluginId: string, defaultTtl: number | undefined) => Keyv; +/* + * TODO(freben): This class intentionally inlines the CacheManagerOptions and + * PluginCacheManager types, to not break the api reports in backend-common + * which re-exports it. When backend-common is deprecated, we can stop inlining + * those types. + */ + /** * Implements a Cache Manager which will automatically create new cache clients * for plugins when requested. All requested cache clients are created with the @@ -47,7 +51,7 @@ export class CacheManager { memory: this.createMemoryStoreFactory(), }; - private readonly logger: LoggerService; + private readonly logger?: LoggerService; private readonly store: keyof CacheManager['storeFactories']; private readonly connection: string; private readonly useRedisSets: boolean; @@ -62,7 +66,18 @@ export class CacheManager { */ static fromConfig( config: Config, - options: CacheManagerOptions = {}, + options: { + /** + * An optional logger for use by the PluginCacheManager. + */ + logger?: LoggerService; + + /** + * An optional handler for connection errors emitted from the underlying data + * store. + */ + onError?: (err: Error) => void; + } = {}, ): CacheManager { // If no `backend.cache` config is provided, instantiate the CacheManager // with an in-memory cache client. @@ -72,27 +87,26 @@ export class CacheManager { config.getOptionalString('backend.cache.connection') || ''; const useRedisSets = config.getOptionalBoolean('backend.cache.useRedisSets') ?? true; - - // TODO: Make logger required and remove the default logger after moving this class to the `backstage-defaults`package - const logger = (options.logger || getRootLogger()).child({ + const logger = options.logger?.child({ type: 'cacheManager', }); return new CacheManager( store, connectionString, useRedisSets, - logger, options.onError, + logger, defaultTtl, ); } - private constructor( + /** @internal */ + constructor( store: string, connectionString: string, useRedisSets: boolean, - logger: LoggerService, errorHandler: CacheManagerOptions['onError'], + logger?: LoggerService, defaultTtl?: number, ) { if (!this.storeFactories.hasOwnProperty(store)) { @@ -112,7 +126,9 @@ export class CacheManager { * @param pluginId - The plugin that the cache manager should be created for. * Plugin names should be unique. */ - forPlugin(pluginId: string): PluginCacheManager { + forPlugin(pluginId: string): { + getClient(options?: CacheServiceOptions): CacheService; + } { return { getClient: (defaultOptions = {}) => { const clientFactory = (options: CacheServiceOptions) => { @@ -124,7 +140,7 @@ export class CacheManager { // Always provide an error handler to avoid stopping the process. concreteClient.on('error', (err: Error) => { // In all cases, just log the error. - this.logger.error('Failed to create cache client', err); + this.logger?.error('Failed to create cache client', err); // Invoke any custom error handler if provided. if (typeof this.errorHandler === 'function') { @@ -149,7 +165,8 @@ export class CacheManager { } private createRedisStoreFactory(): StoreFactory { - let store: KeyvRedis | undefined; + const KeyvRedis = require('@keyv/redis'); + let store: typeof KeyvRedis | undefined; return (pluginId, defaultTtl) => { if (!store) { store = new KeyvRedis(this.connection); @@ -164,7 +181,8 @@ export class CacheManager { } private createMemcacheStoreFactory(): StoreFactory { - let store: KeyvMemcache | undefined; + const KeyvMemcache = require('@keyv/memcache'); + let store: typeof KeyvMemcache | undefined; return (pluginId, defaultTtl) => { if (!store) { store = new KeyvMemcache(this.connection); @@ -187,12 +205,3 @@ export class CacheManager { }); } } - -/** @public */ -export function cacheToPluginCacheManager( - cache: CacheService, -): PluginCacheManager { - return { - getClient: (opts: CacheServiceOptions) => cache.withOptions(opts), - }; -} diff --git a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts index d348c455d2..f60d770644 100644 --- a/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/cache/cacheServiceFactory.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { CacheManager } from '@backstage/backend-common'; import { coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; +import { CacheManager } from './CacheManager'; /** * @public @@ -28,9 +28,10 @@ export const cacheServiceFactory = createServiceFactory({ deps: { config: coreServices.rootConfig, plugin: coreServices.pluginMetadata, + logger: coreServices.rootLogger, }, - async createRootContext({ config }) { - return CacheManager.fromConfig(config); + async createRootContext({ config, logger }) { + return CacheManager.fromConfig(config, { logger }); }, async factory({ plugin }, manager) { return manager.forPlugin(plugin.getId()).getClient(); diff --git a/packages/backend-defaults/src/entrypoints/cache/index.ts b/packages/backend-defaults/src/entrypoints/cache/index.ts index f96ee77182..b16fa56bd2 100644 --- a/packages/backend-defaults/src/entrypoints/cache/index.ts +++ b/packages/backend-defaults/src/entrypoints/cache/index.ts @@ -15,3 +15,5 @@ */ export { cacheServiceFactory } from './cacheServiceFactory'; +export { CacheManager } from './CacheManager'; +export type { CacheManagerOptions, PluginCacheManager } from './types'; diff --git a/packages/backend-defaults/src/entrypoints/cache/types.ts b/packages/backend-defaults/src/entrypoints/cache/types.ts new file mode 100644 index 0000000000..ccb3ed5f6d --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/cache/types.ts @@ -0,0 +1,46 @@ +/* + * 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 { LoggerService } from '@backstage/backend-plugin-api'; +import { + CacheService, + CacheServiceOptions, +} from '@backstage/backend-plugin-api'; + +/** + * Options given when constructing a {@link CacheManager}. + * + * @public + */ +export type CacheManagerOptions = { + /** + * An optional logger for use by the PluginCacheManager. + */ + logger?: LoggerService; + + /** + * An optional handler for connection errors emitted from the underlying data + * store. + */ + onError?: (err: Error) => void; +}; + +/** + * @public + */ +export interface PluginCacheManager { + getClient(options?: CacheServiceOptions): CacheService; +} diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts similarity index 100% rename from packages/backend-common/src/database/DatabaseManager.test.ts rename to packages/backend-defaults/src/entrypoints/database/DatabaseManager.test.ts diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts similarity index 99% rename from packages/backend-common/src/database/DatabaseManager.ts rename to packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts index dd36cc3b4a..f19a848056 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-defaults/src/entrypoints/database/DatabaseManager.ts @@ -41,7 +41,7 @@ function pluginPath(pluginId: string): string { * @public */ export type DatabaseManagerOptions = { - migrations?: PluginDatabaseManager['migrations']; + migrations?: DatabaseService['migrations']; logger?: LoggerService; }; diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultNameOverride.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.test.ts diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultNameOverride.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultNameOverride.ts diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.test.ts diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.ts b/packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.ts similarity index 100% rename from packages/backend-common/src/database/connectors/defaultSchemaOverride.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/defaultSchemaOverride.ts diff --git a/packages/backend-common/src/database/connectors/index.ts b/packages/backend-defaults/src/entrypoints/database/connectors/index.ts similarity index 100% rename from packages/backend-common/src/database/connectors/index.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/index.ts diff --git a/packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.test.ts diff --git a/packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mergeDatabaseConfig.ts diff --git a/packages/backend-common/src/database/connectors/mysql.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mysql.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mysql.test.ts diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts similarity index 100% rename from packages/backend-common/src/database/connectors/mysql.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/mysql.ts diff --git a/packages/backend-common/src/database/connectors/postgres.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/postgres.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/postgres.test.ts diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts similarity index 100% rename from packages/backend-common/src/database/connectors/postgres.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts diff --git a/packages/backend-common/src/database/connectors/sqlite3.test.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts similarity index 100% rename from packages/backend-common/src/database/connectors/sqlite3.test.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.test.ts diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts similarity index 100% rename from packages/backend-common/src/database/connectors/sqlite3.ts rename to packages/backend-defaults/src/entrypoints/database/connectors/sqlite3.ts diff --git a/packages/backend-defaults/src/entrypoints/database/index.ts b/packages/backend-defaults/src/entrypoints/database/index.ts index d676c8013e..7d6221b856 100644 --- a/packages/backend-defaults/src/entrypoints/database/index.ts +++ b/packages/backend-defaults/src/entrypoints/database/index.ts @@ -15,3 +15,9 @@ */ export { databaseServiceFactory } from './databaseServiceFactory'; +export { + DatabaseManager, + type DatabaseManagerOptions, + type LegacyRootDatabaseService, + dropDatabase, +} from './DatabaseManager'; diff --git a/packages/backend-defaults/src/entrypoints/database/types.ts b/packages/backend-defaults/src/entrypoints/database/types.ts new file mode 100644 index 0000000000..a9cceaa1f9 --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/database/types.ts @@ -0,0 +1,100 @@ +/* + * 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 { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { Knex } from 'knex'; + +export type { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; + +/** + * Manages an underlying Knex database driver. + */ +export interface DatabaseConnector { + /** + * Provides an instance of a knex database connector. + */ + createClient( + dbConfig: Config, + overrides?: Partial, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Knex; + + /** + * Provides a partial knex config sufficient to override a database name. + */ + createNameOverride(name: string): Partial; + + /** + * Provides a partial knex config sufficient to override a PostgreSQL schema + * name within utilizing the `searchPath` knex configuration. + */ + createSchemaOverride?(name: string): Partial; + + /** + * Produces a knex connection config object representing a database connection + * string. + */ + parseConnectionString( + connectionString: string, + client?: string, + ): Knex.StaticConnectionConfig; + + /** + * Performs a side-effect to ensure database names passed in are present. + * + * Calling this function on databases which already exist should do nothing. + * Missing databases should be created if needed. + */ + ensureDatabaseExists?( + dbConfig: Config, + ...databases: Array + ): Promise; + + /** + * Performs a side-effect to ensure schema names passed in are present. + * + * Calling this function on schemas which already exist should do nothing. + * Missing schemas should be created if needed. + */ + ensureSchemaExists?( + dbConfig: Config, + ...schemas: Array + ): Promise; + + /** + * Deletes databases. + */ + dropDatabase?(dbConfig: Config, ...databases: Array): Promise; +} + +export interface Connector { + getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise; + + dropDatabase(...databaseNames: string[]): Promise; +} diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index edd462b528..d15399d279 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -14,7 +14,7 @@ import { AwsAlbResult as AwsAlbResult_2 } from '@backstage/plugin-auth-backend-m import { AzureEasyAuthResult } from '@backstage/plugin-auth-backend-module-azure-easyauth-provider'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageSignInResult } from '@backstage/plugin-auth-node'; -import { CacheClient } from '@backstage/backend-common'; +import { CacheService } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { ClientAuthResponse } from '@backstage/plugin-auth-node'; import { cloudflareAccessSignInResolvers } from '@backstage/plugin-auth-backend-module-cloudflare-access-provider'; @@ -452,7 +452,7 @@ export const providers: Readonly<{ signIn: { resolver: SignInResolver_2; }; - cache?: CacheClient | undefined; + cache?: CacheService | undefined; }) => AuthProviderFactory_2; resolvers: Readonly; }>; diff --git a/yarn.lock b/yarn.lock index d006daa23c..157f44465e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3430,6 +3430,7 @@ __metadata: dependencies: "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-dev-utils": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -3439,13 +3440,23 @@ __metadata: "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" "@backstage/types": "workspace:^" + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 "@opentelemetry/api": ^1.3.0 + better-sqlite3: ^9.0.0 cron: ^3.0.0 + fs-extra: ^11.2.0 + keyv: ^4.5.2 knex: ^3.0.0 lodash: ^4.17.21 luxon: ^3.0.0 + mysql2: ^3.0.0 + p-limit: ^3.1.0 + pg: ^8.11.3 + pg-connection-string: ^2.3.0 uuid: ^9.0.0 wait-for-expect: ^3.0.2 + yn: ^4.0.0 zod: ^3.22.4 languageName: unknown linkType: soft From 4728a59c3d185d4606a2f962d8abe5ed187fb30b Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 22 May 2024 08:54:24 -0500 Subject: [PATCH 513/567] Task Schedule Definition Deprecation Correction Signed-off-by: Andre Wanlin --- packages/backend-tasks/src/tasks/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index 955909e040..e6a31873be 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -157,7 +157,7 @@ export interface TaskScheduleDefinition { * that control the scheduling of a task. * * @public - * @deprecated Please import `SchedulerServiceTaskDefinitionConfig` from `@backstage/backend-plugin-api` instead + * @deprecated Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead */ export interface TaskScheduleDefinitionConfig { /** From ed473cd98c2cfca94b0e496f7ceef85f135d27e2 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Wed, 22 May 2024 08:56:41 -0500 Subject: [PATCH 514/567] Added changeset Signed-off-by: Andre Wanlin --- .changeset/forty-adults-roll.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/forty-adults-roll.md diff --git a/.changeset/forty-adults-roll.md b/.changeset/forty-adults-roll.md new file mode 100644 index 0000000000..50dd60da9e --- /dev/null +++ b/.changeset/forty-adults-roll.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` From 9e63318311be4a267adcb7b4b566f69357df73cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 7 May 2024 08:30:04 +0200 Subject: [PATCH 515/567] Implement the scope feature of external access service tokens, as per BEP-0007 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/friendly-keys-fold.md | 6 + .changeset/great-cougars-guess.md | 5 + .changeset/neat-rivers-share.md | 5 + packages/backend-app-api/config.d.ts | 80 ++++++++ .../auth/DefaultAuthService.ts | 22 ++- .../auth/authServiceFactory.test.ts | 60 +++++- .../auth/external/ExternalTokenHandler.ts | 12 +- .../auth/external/helpers.test.ts | 181 ++++++++++++++++++ .../implementations/auth/external/helpers.ts | 144 ++++++++++++++ .../auth/external/legacy.test.ts | 150 +++++++++++---- .../implementations/auth/external/legacy.ts | 39 +++- .../auth/external/static.test.ts | 86 ++++++--- .../implementations/auth/external/static.ts | 28 ++- .../implementations/auth/external/types.ts | 14 +- .../services/implementations/auth/helpers.ts | 3 + .../permissions/permissionsServiceFactory.ts | 4 +- packages/backend-plugin-api/api-report.md | 10 + .../src/services/definitions/AuthService.ts | 49 +++++ .../src/services/definitions/index.ts | 1 + packages/backend-test-utils/api-report.md | 2 + .../src/next/services/mockCredentials.test.ts | 10 + .../src/next/services/mockCredentials.ts | 10 +- plugins/permission-node/api-report.md | 1 + .../src/ServerPermissionClient.test.ts | 66 ++++++- .../src/ServerPermissionClient.ts | 65 ++++++- 25 files changed, 968 insertions(+), 85 deletions(-) create mode 100644 .changeset/friendly-keys-fold.md create mode 100644 .changeset/great-cougars-guess.md create mode 100644 .changeset/neat-rivers-share.md create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/helpers.ts diff --git a/.changeset/friendly-keys-fold.md b/.changeset/friendly-keys-fold.md new file mode 100644 index 0000000000..218abe4efe --- /dev/null +++ b/.changeset/friendly-keys-fold.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': patch +'@backstage/backend-app-api': patch +--- + +Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. diff --git a/.changeset/great-cougars-guess.md b/.changeset/great-cougars-guess.md new file mode 100644 index 0000000000..af1de3e8ce --- /dev/null +++ b/.changeset/great-cougars-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': patch +--- + +Made it possible to give access restrictions to `mockCredentials.service` diff --git a/.changeset/neat-rivers-share.md b/.changeset/neat-rivers-share.md new file mode 100644 index 0000000000..42649de977 --- /dev/null +++ b/.changeset/neat-rivers-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-node': patch +--- + +Ensure that service token access restrictions, when present, are taken into account diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index 5517af4f98..5b72fef54c 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -88,6 +88,46 @@ export interface Config { */ subject: string; }; + /** + * Restricts what types of access that are permitted for this access + * method. If no access restrictions are given, it'll have unlimited + * access. This access restriction applies for the framework level; + * individual plugins may have their own access control mechanisms + * on top of this. + */ + accessRestrictions?: Array<{ + /** + * Permit access to make requests to this plugin. + * + * Can be further refined by setting additional fields below. + */ + plugin: string; + /** + * If given, this method is limited to only performing actions + * with these named permissions in this plugin. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permission?: string | Array; + /** + * If given, this method is limited to only performing actions + * whose permissions have these attributes. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permissionAttribute?: { + /** + * One of more of 'create', 'read', 'update', or 'delete'. + */ + action?: string | Array; + }; + }>; } | { /** @@ -130,6 +170,46 @@ export interface Config { */ subject: string; }; + /** + * Restricts what types of access that are permitted for this access + * method. If no access restrictions are given, it'll have unlimited + * access. This access restriction applies for the framework level; + * individual plugins may have their own access control mechanisms + * on top of this. + */ + accessRestrictions?: Array<{ + /** + * Permit access to make requests to this plugin. + * + * Can be further refined by setting additional fields below. + */ + plugin: string; + /** + * If given, this method is limited to only performing actions + * with these named permissions in this plugin. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permission?: string | Array; + /** + * If given, this method is limited to only performing actions + * whose permissions have these attributes. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permissionAttribute?: { + /** + * One of more of 'create', 'read', 'update', or 'delete'. + */ + action?: string | Array; + }; + }>; } | { /** diff --git a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts index dc76a8761b..d0c11a4eee 100644 --- a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts +++ b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts @@ -23,7 +23,11 @@ import { BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; -import { AuthenticationError, ForwardedError } from '@backstage/errors'; +import { + AuthenticationError, + ForwardedError, + NotAllowedError, +} from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import { decodeJwt } from 'jose'; import { ExternalTokenHandler } from './external/ExternalTokenHandler'; @@ -82,7 +86,21 @@ export class DefaultAuthService implements AuthService { const externalResult = await this.externalTokenHandler.verifyToken(token); if (externalResult) { - return createCredentialsWithServicePrincipal(externalResult.subject); + const restrictions = externalResult.accessRestrictions; + if (restrictions) { + if (!restrictions.has(this.pluginId)) { + const valid = [...restrictions.keys()].map(k => `'${k}'`).join(', '); + throw new NotAllowedError( + `This token's access is restricted to plugin(s) ${valid}`, + ); + } + } + + return createCredentialsWithServicePrincipal( + externalResult.subject, + undefined, + restrictions?.get(this.pluginId), + ); } throw new AuthenticationError('Illegal token'); diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts index 96d4ce4dc9..71fbe64963 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.test.ts @@ -42,7 +42,26 @@ const mockDeps = [ data: { backend: { baseUrl: 'http://localhost', - auth: { keys: [{ secret: 'abc' }] }, + auth: { + keys: [{ secret: 'abc' }], + externalAccess: [ + { + type: 'static', + options: { + token: 'limited-static-token', + subject: 'limited-static-subject', + }, + accessRestrictions: [{ plugin: 'catalog', permission: 'do.it' }], + }, + { + type: 'static', + options: { + token: 'unlimited-static-token', + subject: 'unlimited-static-subject', + }, + }, + ], + }, }, }, }), @@ -385,4 +404,43 @@ describe('authServiceFactory', () => { "Unable to call 'kubernetes' plugin on behalf of user, because the target plugin does not support on-behalf-of tokens or the plugin doesn't exist", ); }); + + it('should eagerly reject access to external access tokens based on plugin id', async () => { + const tester = ServiceFactoryTester.from(authServiceFactory, { + dependencies: mockDeps, + }); + + const catalogAuth = await tester.get('catalog'); + + await expect( + catalogAuth.authenticate('limited-static-token'), + ).resolves.toMatchObject({ + principal: { + subject: 'limited-static-subject', + accessRestrictions: { permissionNames: ['do.it'] }, + }, + }); + + await expect( + catalogAuth.authenticate('unlimited-static-token'), + ).resolves.toMatchObject({ + principal: { + subject: 'unlimited-static-subject', + }, + }); + + const scaffolderAuth = await tester.get('scaffolder'); + + await expect( + scaffolderAuth.authenticate('limited-static-token'), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"This token's access is restricted to plugin(s) 'catalog'"`, + ); + + await expect( + scaffolderAuth.authenticate('unlimited-static-token'), + ).resolves.toMatchObject({ + principal: { subject: 'unlimited-static-subject' }, + }); + }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts index 79ad8dd3c4..82603953d0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts @@ -20,8 +20,8 @@ import { } from '@backstage/backend-plugin-api'; import { LegacyTokenHandler } from './legacy'; import { StaticTokenHandler } from './static'; -import { TokenHandler } from './types'; import { JWKSHandler } from './jwks'; +import { AccessRestriptionsMap, TokenHandler } from './types'; const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; const OLD_CONFIG_KEY = 'backend.auth.keys'; @@ -61,7 +61,7 @@ export class ExternalTokenHandler { `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`, ); } - handler.add(handlerConfig.getConfig('options')); + handler.add(handlerConfig); } // Load the old keys too @@ -80,7 +80,13 @@ export class ExternalTokenHandler { constructor(private readonly handlers: TokenHandler[]) {} - async verifyToken(token: string): Promise<{ subject: string } | undefined> { + async verifyToken(token: string): Promise< + | { + subject: string; + accessRestrictions?: AccessRestriptionsMap; + } + | undefined + > { for (const handler of this.handlers) { const result = await handler.verifyToken(token); if (result) { diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts new file mode 100644 index 0000000000..ee0dc3cb87 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts @@ -0,0 +1,181 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { readAccessRestrictionsFromConfig } from './helpers'; +import { JsonObject } from '@backstage/types'; + +describe('readAccessRestrictionsFromConfig', () => { + function r(config: JsonObject) { + return readAccessRestrictionsFromConfig(new ConfigReader(config)); + } + + it('handles empty / missing restrictions', () => { + expect(r({})).toBeUndefined(); + expect(r({ accessRestrictions: [] })).toBeUndefined(); + }); + + it('handles type errors', () => { + expect(() => + r({ accessRestrictions: 7 }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions' in 'mock-config', got number, wanted object-array"`, + ); + expect(() => + r({ accessRestrictions: ['hello'] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0]' in 'mock-config', got string, wanted object-array"`, + ); + expect(() => + r({ accessRestrictions: [{ unknown: {} }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid key 'unknown' in 'accessRestrictions' config, expected one of 'plugin', 'permission', 'permissionAttribute'"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 7 }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].plugin' in 'mock-config', got number, wanted string"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 'valid', permission: 7 }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permission' in 'mock-config', got number, wanted string"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 'valid', permission: [7] }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permission[0]' in 'mock-config', got number, wanted string-array"`, + ); + expect(() => + r({ accessRestrictions: [{ plugin: 'valid', permissionAttribute: 7 }] }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permissionAttribute' in 'mock-config', got number, wanted object"`, + ); + expect(() => + r({ + accessRestrictions: [ + { plugin: 'valid', permissionAttribute: { a: [] } }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid key 'a' in 'permissionAttribute' config, expected 'action'"`, + ); + expect(() => + r({ + accessRestrictions: [ + { plugin: 'valid', permissionAttribute: { action: 7 } }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].permissionAttribute.action' in 'mock-config', got number, wanted string"`, + ); + expect(() => + r({ + accessRestrictions: [ + { plugin: 'valid', permissionAttribute: { action: 'wrong' } }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid value 'wrong' at 'action' in 'permissionAttributes' config, valid values are 'create', 'read', 'update', 'delete'"`, + ); + }); + + it('parses valid access restrictions', () => { + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: {}, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permission: 'a, b a', + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionNames: ['a', 'b'] }, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permission: ['a', 'b', 'a'], + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionNames: ['a', 'b'] }, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permissionAttribute: { action: 'read, update read' }, + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionAttributes: { action: ['read', 'update'] } }, + }), + ), + ); + + expect( + r({ + accessRestrictions: [ + { + plugin: 'a', + permissionAttribute: { action: ['read', 'update', 'read'] }, + }, + ], + }), + ).toEqual( + new Map( + Object.entries({ + a: { permissionAttributes: { action: ['read', 'update'] } }, + }), + ), + ); + }); +}); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts new file mode 100644 index 0000000000..5d199a5067 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts @@ -0,0 +1,144 @@ +/* + * 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 { Config } from '@backstage/config'; +import { AccessRestriptionsMap } from './types'; + +/** + * Parses and returns the `accessRestrictions` configuration from an + * `externalAccess` entry, or undefined if there wasn't one. + * + * @internal + */ +export function readAccessRestrictionsFromConfig( + externalAccessEntryConfig: Config, +): AccessRestriptionsMap | undefined { + const configs = + externalAccessEntryConfig.getOptionalConfigArray('accessRestrictions') ?? + []; + + const result: AccessRestriptionsMap = new Map(); + for (const config of configs) { + const validKeys = ['plugin', 'permission', 'permissionAttribute']; + for (const key of config.keys()) { + if (!validKeys.includes(key)) { + const valid = validKeys.map(k => `'${k}'`).join(', '); + throw new Error( + `Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`, + ); + } + } + + const pluginId = config.getString('plugin'); + const permissionNames = readPermissionNames(config); + const permissionAttributes = readPermissionAttributes(config); + + if (result.has(pluginId)) { + throw new Error( + `Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`, + ); + } + + result.set(pluginId, { + ...(permissionNames ? { permissionNames } : {}), + ...(permissionAttributes ? { permissionAttributes } : {}), + }); + } + + return result.size ? result : undefined; +} + +/** + * Reads a config value as a string or an array of strings, and deduplicates and + * splits by comma/space into a string array. Can also validate against a known + * set of values. Returns undefined if the key didn't exist or if the array + * would have ended up being empty. + */ +function stringOrStringArray( + root: Config, + key: string, + validValues?: readonly T[], +): T[] | undefined { + if (!root.has(key)) { + return undefined; + } + + const rawValues = Array.isArray(root.get(key)) + ? root.getStringArray(key) + : [root.getString(key)]; + + const values = [ + ...new Set( + rawValues + .map(v => v.split(/[ ,]/)) + .flat() + .filter(Boolean), + ), + ]; + + if (!values.length) { + return undefined; + } + + if (validValues?.length) { + for (const value of values) { + if (!validValues.includes(value as T)) { + const valid = validValues.map(k => `'${k}'`).join(', '); + throw new Error( + `Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`, + ); + } + } + } + + return values as T[]; +} + +function readPermissionNames(externalAccessEntryConfig: Config) { + return stringOrStringArray(externalAccessEntryConfig, 'permission'); +} + +function readPermissionAttributes(externalAccessEntryConfig: Config) { + const config = externalAccessEntryConfig.getOptionalConfig( + 'permissionAttribute', + ); + if (!config) { + return undefined; + } + + const validKeys = ['action']; + for (const key of config.keys()) { + if (!validKeys.includes(key)) { + const valid = validKeys.map(k => `'${k}'`).join(', '); + throw new Error( + `Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`, + ); + } + } + + const action = stringOrStringArray(config, 'action', [ + 'create', + 'read', + 'update', + 'delete', + ]); + + const result = { + ...(action ? { action } : {}), + }; + + return Object.keys(result).length ? result : undefined; +} diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts index 8b2469f6b7..503157acc7 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts @@ -25,17 +25,35 @@ describe('LegacyTokenHandler', () => { const key1 = randomBytes(24); const key2 = randomBytes(24); const key3 = randomBytes(24); + const accessRestrictions1 = new Map( + Object.entries({ + scaffolder: {}, + }), + ); + const accessRestrictions2 = new Map( + Object.entries({ + catalog: { permissionNames: ['catalog.entity.read'] }, + }), + ); tokenHandler.add( new ConfigReader({ - secret: key1.toString('base64'), - subject: 'key1', + options: { + secret: key1.toString('base64'), + subject: 'key1', + }, + accessRestrictions: [{ plugin: 'scaffolder' }], }), ); tokenHandler.add( new ConfigReader({ - secret: key2.toString('base64'), - subject: 'key2', + options: { + secret: key2.toString('base64'), + subject: 'key2', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], }), ); tokenHandler.addOld( @@ -54,6 +72,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token1)).resolves.toEqual({ subject: 'key1', + accessRestrictions: accessRestrictions1, }); const token2 = await new SignJWT({ @@ -65,6 +84,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token2)).resolves.toEqual({ subject: 'key2', + accessRestrictions: accessRestrictions2, }); const token3 = await new SignJWT({ @@ -147,39 +167,93 @@ describe('LegacyTokenHandler', () => { // new style add, bad secrets expect(() => - handler.add(new ConfigReader({ _missingsecret: true, subject: 'ok' })), - ).toThrow(/secret/); + handler.add( + new ConfigReader({ options: { _missingsecret: true, subject: 'ok' } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'options.secret' in 'mock-config'"`, + ); expect(() => - handler.add(new ConfigReader({ secret: '', subject: 'ok' })), - ).toThrow(/secret/); + handler.add(new ConfigReader({ options: { secret: '', subject: 'ok' } })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.secret' in 'mock-config', got empty-string, wanted string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 'has spaces', subject: 'ok' })), - ).toThrow(/secret/); + handler.add( + new ConfigReader({ options: { secret: 'has spaces', subject: 'ok' } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 'hasnewline\n', subject: 'ok' })), - ).toThrow(/secret/); + handler.add( + new ConfigReader({ + options: { secret: 'hasnewline\n', subject: 'ok' }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 3, subject: 'ok' })), - ).toThrow(/secret/); + handler.add(new ConfigReader({ options: { secret: 3, subject: 'ok' } })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.secret' in 'mock-config', got number, wanted string"`, + ); // new style add, bad subjects expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', _missingsubject: true })), - ).toThrow(/subject/); - expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', subject: '' })), - ).toThrow(/subject/); - expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', subject: 'has spaces' })), - ).toThrow(/subject/); + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', _missingsubject: true }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'options.subject' in 'mock-config'"`, + ); expect(() => handler.add( - new ConfigReader({ secret: 'b2s=', subject: 'hasnewline\n' }), + new ConfigReader({ options: { secret: 'b2s=', subject: '' } }), ), - ).toThrow(/subject/); + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.subject' in 'mock-config', got empty-string, wanted string"`, + ); expect(() => - handler.add(new ConfigReader({ secret: 'b2s=', subject: 3 })), - ).toThrow(/subject/); + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', subject: 'has spaces' }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal subject, must be a set of non-space characters"`, + ); + expect(() => + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', subject: 'hasnewline\n' }, + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal subject, must be a set of non-space characters"`, + ); + expect(() => + handler.add( + new ConfigReader({ options: { secret: 'b2s=', subject: 3 } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.subject' in 'mock-config', got number, wanted string"`, + ); + + // new style add, bad access restrictions + expect(() => + handler.add( + new ConfigReader({ + options: { secret: 'b2s=', subject: 'subject' }, + accessRestrictions: [{ plugin: ['a'] }], + }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'accessRestrictions[0].plugin' in 'mock-config', got array, wanted string"`, + ); // old style add expect(() => @@ -187,18 +261,28 @@ describe('LegacyTokenHandler', () => { ).not.toThrow(); expect(() => handler.addOld(new ConfigReader({ _missingsecret: true })), - ).toThrow(/secret/); - expect(() => handler.addOld(new ConfigReader({ secret: '' }))).toThrow( - /secret/, + ).toThrowErrorMatchingInlineSnapshot( + `"Missing required config value at 'secret' in 'mock-config'"`, + ); + expect(() => + handler.addOld(new ConfigReader({ secret: '' })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'secret' in 'mock-config', got empty-string, wanted string"`, ); expect(() => handler.addOld(new ConfigReader({ secret: 'has spaces' })), - ).toThrow(/secret/); + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); expect(() => handler.addOld(new ConfigReader({ secret: 'hasnewline\n' })), - ).toThrow(/secret/); - expect(() => handler.addOld(new ConfigReader({ secret: 3 }))).toThrow( - /secret/, + ).toThrowErrorMatchingInlineSnapshot( + `"Illegal secret, must be a valid base64 string"`, + ); + expect(() => + handler.addOld(new ConfigReader({ secret: 3 })), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'secret' in 'mock-config', got number, wanted string"`, ); }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts index 8448f295a6..ba56d3a213 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts @@ -16,7 +16,8 @@ import { Config } from '@backstage/config'; import { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose'; -import { TokenHandler } from './types'; +import { readAccessRestrictionsFromConfig } from './helpers'; +import { AccessRestriptionsMap, TokenHandler } from './types'; /** * Handles `type: legacy` access. @@ -24,19 +25,32 @@ import { TokenHandler } from './types'; * @internal */ export class LegacyTokenHandler implements TokenHandler { - #entries: Array<{ key: Uint8Array; subject: string }> = []; + #entries = new Array<{ + key: Uint8Array; + subject: string; + accessRestrictions?: AccessRestriptionsMap; + }>(); - add(options: Config) { - this.#doAdd(options.getString('secret'), options.getString('subject')); + add(config: Config) { + const accessRestrictions = readAccessRestrictionsFromConfig(config); + this.#doAdd( + config.getString('options.secret'), + config.getString('options.subject'), + accessRestrictions, + ); } // used only for the old backend.auth.keys array - addOld(options: Config) { + addOld(config: Config) { // This choice of subject is for compatibility reasons - this.#doAdd(options.getString('secret'), 'external:backstage-plugin'); + this.#doAdd(config.getString('secret'), 'external:backstage-plugin'); } - #doAdd(secret: string, subject: string) { + #doAdd( + secret: string, + subject: string, + accessRestrictions?: AccessRestriptionsMap, + ) { if (!secret.match(/^\S+$/)) { throw new Error('Illegal secret, must be a valid base64 string'); } @@ -52,7 +66,11 @@ export class LegacyTokenHandler implements TokenHandler { throw new Error('Illegal subject, must be a set of non-space characters'); } - this.#entries.push({ key, subject }); + this.#entries.push({ + key, + subject, + accessRestrictions, + }); } async verifyToken(token: string) { @@ -79,7 +97,10 @@ export class LegacyTokenHandler implements TokenHandler { for (const entry of this.#entries) { try { await jwtVerify(token, entry.key); - return { subject: entry.subject }; + return { + subject: entry.subject, + accessRestrictions: entry.accessRestrictions, + }; } catch (e) { if (e.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') { throw e; diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts index 86bdf0bdee..458c3f06f3 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts @@ -20,14 +20,36 @@ import { StaticTokenHandler } from './static'; describe('StaticTokenHandler', () => { it('accepts any of the added list of tokens', async () => { const handler = new StaticTokenHandler(); - handler.add(new ConfigReader({ token: 'abcabcabc', subject: 'one' })); - handler.add(new ConfigReader({ token: 'defdefdef', subject: 'two' })); + handler.add( + new ConfigReader({ + options: { token: 'abcabcabc', subject: 'one' }, + accessRestrictions: [{ plugin: 'scaffolder' }], + }), + ); + handler.add( + new ConfigReader({ + options: { token: 'defdefdef', subject: 'two' }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }), + ); + const accessRestrictionsOne = new Map(Object.entries({ scaffolder: {} })); + const accessRestrictionsTwo = new Map( + Object.entries({ + catalog: { + permissionNames: ['catalog.entity.read'], + }, + }), + ); await expect(handler.verifyToken('abcabcabc')).resolves.toEqual({ subject: 'one', + accessRestrictions: accessRestrictionsOne, }); await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ subject: 'two', + accessRestrictions: accessRestrictionsTwo, }); await expect(handler.verifyToken('ghighighi')).resolves.toBeUndefined(); }); @@ -41,71 +63,89 @@ describe('StaticTokenHandler', () => { const handler = new StaticTokenHandler(); expect(() => - handler.add(new ConfigReader({ _missingtoken: true, subject: 'ok' })), + handler.add( + new ConfigReader({ options: { _missingtoken: true, subject: 'ok' } }), + ), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'token' in 'mock-config'"`, + `"Missing required config value at 'options.token' in 'mock-config'"`, ); expect(() => - handler.add(new ConfigReader({ token: '', subject: 'ok' })), + handler.add(new ConfigReader({ options: { token: '', subject: 'ok' } })), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'token' in 'mock-config', got empty-string, wanted string"`, + `"Invalid type in config for key 'options.token' in 'mock-config', got empty-string, wanted string"`, ); expect(() => - handler.add(new ConfigReader({ token: 'has spaces', subject: 'ok' })), + handler.add( + new ConfigReader({ options: { token: 'has spaces', subject: 'ok' } }), + ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be a set of non-space characters"`, ); expect(() => handler.add( new ConfigReader({ - token: 'hasnewlinebutislongenough\n', - subject: 'ok', + options: { + token: 'hasnewlinebutislongenough\n', + subject: 'ok', + }, }), ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be a set of non-space characters"`, ); expect(() => - handler.add(new ConfigReader({ token: 'short', subject: 'ok' })), + handler.add( + new ConfigReader({ options: { token: 'short', subject: 'ok' } }), + ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal token, must be at least 8 characters length"`, ); expect(() => - handler.add(new ConfigReader({ token: 3, subject: 'ok' })), + handler.add(new ConfigReader({ options: { token: 3, subject: 'ok' } })), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'token' in 'mock-config', got number, wanted string"`, + `"Invalid type in config for key 'options.token' in 'mock-config', got number, wanted string"`, ); expect(() => handler.add( - new ConfigReader({ token: 'validtoken', _missingsubject: true }), + new ConfigReader({ + options: { token: 'validtoken', _missingsubject: true }, + }), ), ).toThrowErrorMatchingInlineSnapshot( - `"Missing required config value at 'subject' in 'mock-config'"`, - ); - expect(() => - handler.add(new ConfigReader({ token: 'validtoken', subject: '' })), - ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'subject' in 'mock-config', got empty-string, wanted string"`, + `"Missing required config value at 'options.subject' in 'mock-config'"`, ); expect(() => handler.add( - new ConfigReader({ token: 'validtoken', subject: 'has spaces' }), + new ConfigReader({ options: { token: 'validtoken', subject: '' } }), + ), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'options.subject' in 'mock-config', got empty-string, wanted string"`, + ); + expect(() => + handler.add( + new ConfigReader({ + options: { token: 'validtoken', subject: 'has spaces' }, + }), ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal subject, must be a set of non-space characters"`, ); expect(() => handler.add( - new ConfigReader({ token: 'validtoken', subject: 'hasnewline\n' }), + new ConfigReader({ + options: { token: 'validtoken', subject: 'hasnewline\n' }, + }), ), ).toThrowErrorMatchingInlineSnapshot( `"Illegal subject, must be a set of non-space characters"`, ); expect(() => - handler.add(new ConfigReader({ token: 'validtoken', subject: 3 })), + handler.add( + new ConfigReader({ options: { token: 'validtoken', subject: 3 } }), + ), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid type in config for key 'subject' in 'mock-config', got number, wanted string"`, + `"Invalid type in config for key 'options.subject' in 'mock-config', got number, wanted string"`, ); }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.ts index bae8e05f2b..8242b87dc0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.ts @@ -15,7 +15,8 @@ */ import { Config } from '@backstage/config'; -import { TokenHandler } from './types'; +import { readAccessRestrictionsFromConfig } from './helpers'; +import { AccessRestriptionsMap, TokenHandler } from './types'; const MIN_TOKEN_LENGTH = 8; @@ -25,10 +26,14 @@ const MIN_TOKEN_LENGTH = 8; * @internal */ export class StaticTokenHandler implements TokenHandler { - #entries: Array<{ token: string; subject: string }> = []; + #entries = new Array<{ + token: string; + subject: string; + accessRestrictions?: AccessRestriptionsMap; + }>(); - add(options: Config) { - const token = options.getString('token'); + add(config: Config) { + const token = config.getString('options.token'); if (!token.match(/^\S+$/)) { throw new Error('Illegal token, must be a set of non-space characters'); } @@ -38,12 +43,18 @@ export class StaticTokenHandler implements TokenHandler { ); } - const subject = options.getString('subject'); + const subject = config.getString('options.subject'); if (!subject.match(/^\S+$/)) { throw new Error('Illegal subject, must be a set of non-space characters'); } - this.#entries.push({ token, subject }); + const accessRestrictions = readAccessRestrictionsFromConfig(config); + + this.#entries.push({ + token, + subject, + accessRestrictions, + }); } async verifyToken(token: string) { @@ -52,6 +63,9 @@ export class StaticTokenHandler implements TokenHandler { return undefined; } - return { subject: entry.subject }; + return { + subject: entry.subject, + accessRestrictions: entry.accessRestrictions, + }; } } diff --git a/packages/backend-app-api/src/services/implementations/auth/external/types.ts b/packages/backend-app-api/src/services/implementations/auth/external/types.ts index 5d33f09a22..54e51c1ef6 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/types.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/types.ts @@ -14,9 +14,21 @@ * limitations under the License. */ +import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +export type AccessRestriptionsMap = Map< + string, // plugin ID + BackstagePrincipalAccessRestrictions +>; + export interface TokenHandler { add(options: Config): void; - verifyToken(token: string): Promise<{ subject: string } | undefined>; + verifyToken(token: string): Promise< + | { + subject: string; + accessRestrictions?: AccessRestriptionsMap; + } + | undefined + >; } diff --git a/packages/backend-app-api/src/services/implementations/auth/helpers.ts b/packages/backend-app-api/src/services/implementations/auth/helpers.ts index 01fb9537a1..eebe45eb76 100644 --- a/packages/backend-app-api/src/services/implementations/auth/helpers.ts +++ b/packages/backend-app-api/src/services/implementations/auth/helpers.ts @@ -17,6 +17,7 @@ import { BackstageCredentials, BackstageNonePrincipal, + BackstagePrincipalAccessRestrictions, BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; @@ -25,6 +26,7 @@ import { InternalBackstageCredentials } from './types'; export function createCredentialsWithServicePrincipal( sub: string, token?: string, + accessRestrictions?: BackstagePrincipalAccessRestrictions, ): InternalBackstageCredentials { return { $$type: '@backstage/BackstageCredentials', @@ -33,6 +35,7 @@ export function createCredentialsWithServicePrincipal( principal: { type: 'service', subject: sub, + accessRestrictions, }, }; } diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts index c8fa0e7bbf..ecdaa1e938 100644 --- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts @@ -31,12 +31,14 @@ export const permissionsServiceFactory = createServiceFactory({ config: coreServices.rootConfig, discovery: coreServices.discovery, tokenManager: coreServices.tokenManager, + pluginMetadata: coreServices.pluginMetadata, }, - async factory({ auth, config, discovery, tokenManager }) { + async factory({ auth, config, discovery, tokenManager, pluginMetadata }) { return ServerPermissionClient.fromConfig(config, { auth, discovery, tokenManager, + pluginId: pluginMetadata.getId(), }); }, }); diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index c65fc5cf3e..134e4b61da 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -16,6 +16,7 @@ import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; +import { PermissionAttributes } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { QueryPermissionResponse } from '@backstage/plugin-permission-common'; @@ -136,6 +137,14 @@ export type BackstageNonePrincipal = { type: 'none'; }; +// @public +export type BackstagePrincipalAccessRestrictions = { + permissionNames?: string[]; + permissionAttributes?: { + action?: Array['action']>; + }; +}; + // @public (undocumented) export type BackstagePrincipalTypes = { user: BackstageUserPrincipal; @@ -148,6 +157,7 @@ export type BackstagePrincipalTypes = { export type BackstageServicePrincipal = { type: 'service'; subject: string; + accessRestrictions?: BackstagePrincipalAccessRestrictions; }; // @public (undocumented) diff --git a/packages/backend-plugin-api/src/services/definitions/AuthService.ts b/packages/backend-plugin-api/src/services/definitions/AuthService.ts index 827b4122cd..000ce8bbb1 100644 --- a/packages/backend-plugin-api/src/services/definitions/AuthService.ts +++ b/packages/backend-plugin-api/src/services/definitions/AuthService.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { PermissionAttributes } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; /** @@ -40,6 +41,54 @@ export type BackstageServicePrincipal = { // Exact format TBD, possibly 'plugin:' or 'external:' subject: string; + + /** + * The access restrictions that apply to this principal. + * + * @remarks + * + * If no access restrictions are provided the principal is assumed to have + * unlimited access, at a framework level. The permissions system and + * individual plugins may or may not still apply additional access controls on + * top of this. + */ + accessRestrictions?: BackstagePrincipalAccessRestrictions; +}; + +/** + * The access restrictions that apply to a given principal. + * + * @public + */ +export type BackstagePrincipalAccessRestrictions = { + /** + * If given, the principal is limited to only performing actions with these + * named permissions. + * + * Note that this only applies where permissions checks are enabled in the + * first place. Endpoints that are not protected by the permissions system at + * all, are not affected by this setting. + * + * This array always has at least one element, or is missing entirely. + */ + permissionNames?: string[]; + /** + * If given, the principal is limited to only performing actions whose + * permissions have these attributes. + * + * Note that this only applies where permissions checks are enabled in the + * first place. Endpoints that are not protected by the permissions system at + * all, are not affected by this setting. + * + * This object always has at least one key, or is missing entirely. + */ + permissionAttributes?: { + /** + * Match any of these action values. This array always has at least one + * element, or is missing entirely. + */ + action?: Array['action']>; + }; }; /** diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 5739add90c..f22213a285 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -20,6 +20,7 @@ export type { BackstageCredentials, BackstageUserPrincipal, BackstageServicePrincipal, + BackstagePrincipalAccessRestrictions, BackstagePrincipalTypes, BackstageNonePrincipal, } from './AuthService'; diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 4f6eb47243..5a192cb61f 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -11,6 +11,7 @@ import { Backend } from '@backstage/backend-app-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; import { BackstageNonePrincipal } from '@backstage/backend-plugin-api'; +import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; import { BackstageServicePrincipal } from '@backstage/backend-plugin-api'; import { BackstageUserInfo } from '@backstage/backend-plugin-api'; import { BackstageUserPrincipal } from '@backstage/backend-plugin-api'; @@ -68,6 +69,7 @@ export namespace mockCredentials { } export function service( subject?: string, + accessRestrictions?: BackstagePrincipalAccessRestrictions, ): BackstageCredentials; export namespace service { export function header(options?: TokenOptions): string; diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts b/packages/backend-test-utils/src/next/services/mockCredentials.test.ts index ed0071d4b8..343d6d6d20 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.test.ts +++ b/packages/backend-test-utils/src/next/services/mockCredentials.test.ts @@ -134,6 +134,16 @@ describe('mockCredentials', () => { expect(mockCredentials.service.invalidHeader()).toBe( 'Bearer mock-invalid-service-token', ); + expect( + mockCredentials.service('test', { permissionNames: ['do.it'] }), + ).toEqual({ + $$type: '@backstage/BackstageCredentials', + principal: { + type: 'service', + subject: 'test', + accessRestrictions: { permissionNames: ['do.it'] }, + }, + }); }); it('should throw on invalid user entity refs', () => { diff --git a/packages/backend-test-utils/src/next/services/mockCredentials.ts b/packages/backend-test-utils/src/next/services/mockCredentials.ts index 16d2381c73..4214320142 100644 --- a/packages/backend-test-utils/src/next/services/mockCredentials.ts +++ b/packages/backend-test-utils/src/next/services/mockCredentials.ts @@ -17,6 +17,7 @@ import { BackstageCredentials, BackstageNonePrincipal, + BackstagePrincipalAccessRestrictions, BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; @@ -202,14 +203,19 @@ export namespace mockCredentials { /** * Creates a mocked credentials object for a service principal. * - * The default subject is 'external:test-service'. + * The default subject is 'external:test-service', and no access restrictions. */ export function service( subject: string = DEFAULT_MOCK_SERVICE_SUBJECT, + accessRestrictions?: BackstagePrincipalAccessRestrictions, ): BackstageCredentials { return { $$type: '@backstage/BackstageCredentials', - principal: { type: 'service', subject }, + principal: { + type: 'service', + subject, + ...(accessRestrictions ? { accessRestrictions } : {}), + }, }; } diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 7d2910d391..fab3bc7742 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -291,6 +291,7 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; + pluginId?: string; }, ): ServerPermissionClient; } diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 215f624fe9..68c675745a 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -48,7 +48,9 @@ const discovery: PluginEndpointDiscovery = { }; const testBasicPermission = createPermission({ name: 'test.permission', - attributes: {}, + attributes: { + action: 'create', + }, }); const testResourcePermission = createPermission({ @@ -362,4 +364,66 @@ describe('ServerPermissionClient', () => { }); }); }); + + describe('with access restrictions', () => { + it('short circuits the response when relevant access restrictions are present', async () => { + const client = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager: mockServices.tokenManager(), + auth: mockServices.auth(), + pluginId: 'test', + }); + + // no restrictions for the given plugin + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', {}), + }), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching permission name + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionNames: [testBasicPermission.name, 'other'], + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching attributes + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionAttributes: { + action: [testBasicPermission.attributes.action!, 'other' as any], + }, + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching permission name but not attributes + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionNames: [testBasicPermission.name], + permissionAttributes: { + action: ['other' as any], + }, + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + + // matching attributes but not permission name + await expect( + client.authorize([{ permission: testBasicPermission }], { + credentials: mockCredentials.service('foo', { + permissionNames: ['wrong-name'], + permissionAttributes: { + action: [testBasicPermission.attributes.action!], + }, + }), + }), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + }); + }); }); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index f8570fee74..2c2ccefbf4 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -33,18 +33,21 @@ import { AuthorizePermissionResponse, PolicyDecision, QueryPermissionRequest, + DefinitivePolicyDecision, } from '@backstage/plugin-permission-common'; /** * A thin wrapper around - * {@link @backstage/plugin-permission-common#PermissionClient} that allows all - * service-to-service requests. + * {@link @backstage/plugin-permission-common#PermissionClient} that ensures the + * proper short-circuit handling of service principals. + * * @public */ export class ServerPermissionClient implements PermissionsService { readonly #auth: AuthService; readonly #permissionClient: PermissionClient; readonly #permissionEnabled: boolean; + readonly #pluginId?: string; static fromConfig( config: Config, @@ -52,6 +55,7 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; + pluginId?: string; }, ) { const { discovery, tokenManager } = options; @@ -74,6 +78,7 @@ export class ServerPermissionClient implements PermissionsService { auth, permissionClient, permissionEnabled, + pluginId: options.pluginId, }); } @@ -81,16 +86,26 @@ export class ServerPermissionClient implements PermissionsService { auth: AuthService; permissionClient: PermissionClient; permissionEnabled: boolean; + pluginId?: string; }) { this.#auth = options.auth; this.#permissionClient = options.permissionClient; this.#permissionEnabled = options.permissionEnabled; + this.#pluginId = options.pluginId; } async authorizeConditional( queries: QueryPermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { + const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + queries, + options, + ); + if (maybeResponse) { + return maybeResponse; + } + if (await this.#shouldPermissionsBeApplied(options)) { return this.#permissionClient.authorizeConditional( queries, @@ -105,6 +120,14 @@ export class ServerPermissionClient implements PermissionsService { requests: AuthorizePermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { + const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + requests, + options, + ); + if (maybeResponse) { + return maybeResponse; + } + if (await this.#shouldPermissionsBeApplied(options)) { return this.#permissionClient.authorize( requests, @@ -130,6 +153,44 @@ export class ServerPermissionClient implements PermissionsService { return options; } + #decideBasedOnPrincipalAccessRestrictions( + requests: Array, + options?: PermissionsServiceRequestOptions, + ): DefinitivePolicyDecision[] | undefined { + if (!options || !('credentials' in options)) { + return undefined; + } + + // Bail out to the old behavior if + // - the principal is not a service + // - the principal was apparently unrestricted + // - we are in legacy mode because nobody passed in a plugin ID + const credentials = options.credentials; + if ( + !this.#auth.isPrincipal(credentials, 'service') || + !credentials.principal.accessRestrictions || + !this.#pluginId + ) { + return undefined; + } + + const { permissionNames, permissionAttributes } = + credentials.principal.accessRestrictions; + + return requests.map(query => { + if (permissionNames && !permissionNames.includes(query.permission.name)) { + return { result: AuthorizeResult.DENY }; + } + if (permissionAttributes?.action) { + const action = query.permission.attributes?.action; + if (!action || !permissionAttributes.action.includes(action)) { + return { result: AuthorizeResult.DENY }; + } + } + return { result: AuthorizeResult.ALLOW }; + }); + } + async #shouldPermissionsBeApplied( options?: PermissionsServiceRequestOptions, ) { From b155d854bc01685522bdbdff01f57bf19413dc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 19 May 2024 22:50:13 +0200 Subject: [PATCH 516/567] skip the plugin id for permissions client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../permissions/permissionsServiceFactory.ts | 4 +--- plugins/permission-node/api-report.md | 1 - .../permission-node/src/ServerPermissionClient.test.ts | 1 - plugins/permission-node/src/ServerPermissionClient.ts | 9 +-------- 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts index ecdaa1e938..c8fa0e7bbf 100644 --- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts @@ -31,14 +31,12 @@ export const permissionsServiceFactory = createServiceFactory({ config: coreServices.rootConfig, discovery: coreServices.discovery, tokenManager: coreServices.tokenManager, - pluginMetadata: coreServices.pluginMetadata, }, - async factory({ auth, config, discovery, tokenManager, pluginMetadata }) { + async factory({ auth, config, discovery, tokenManager }) { return ServerPermissionClient.fromConfig(config, { auth, discovery, tokenManager, - pluginId: pluginMetadata.getId(), }); }, }); diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index fab3bc7742..7d2910d391 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -291,7 +291,6 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; - pluginId?: string; }, ): ServerPermissionClient; } diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 68c675745a..9becad4d7b 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -371,7 +371,6 @@ describe('ServerPermissionClient', () => { discovery, tokenManager: mockServices.tokenManager(), auth: mockServices.auth(), - pluginId: 'test', }); // no restrictions for the given plugin diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index 2c2ccefbf4..6e3901d65b 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -47,7 +47,6 @@ export class ServerPermissionClient implements PermissionsService { readonly #auth: AuthService; readonly #permissionClient: PermissionClient; readonly #permissionEnabled: boolean; - readonly #pluginId?: string; static fromConfig( config: Config, @@ -55,7 +54,6 @@ export class ServerPermissionClient implements PermissionsService { discovery: DiscoveryService; tokenManager: TokenManager; auth?: AuthService; - pluginId?: string; }, ) { const { discovery, tokenManager } = options; @@ -78,7 +76,6 @@ export class ServerPermissionClient implements PermissionsService { auth, permissionClient, permissionEnabled, - pluginId: options.pluginId, }); } @@ -86,12 +83,10 @@ export class ServerPermissionClient implements PermissionsService { auth: AuthService; permissionClient: PermissionClient; permissionEnabled: boolean; - pluginId?: string; }) { this.#auth = options.auth; this.#permissionClient = options.permissionClient; this.#permissionEnabled = options.permissionEnabled; - this.#pluginId = options.pluginId; } async authorizeConditional( @@ -164,12 +159,10 @@ export class ServerPermissionClient implements PermissionsService { // Bail out to the old behavior if // - the principal is not a service // - the principal was apparently unrestricted - // - we are in legacy mode because nobody passed in a plugin ID const credentials = options.credentials; if ( !this.#auth.isPrincipal(credentials, 'service') || - !credentials.principal.accessRestrictions || - !this.#pluginId + !credentials.principal.accessRestrictions ) { return undefined; } From 17be3e69621929134fa80fde8c2389e865782f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 20 May 2024 11:27:46 +0200 Subject: [PATCH 517/567] one token manager per plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../auth/DefaultAuthService.ts | 18 +----- .../auth/authServiceFactory.ts | 19 ++----- .../external/ExternalTokenHandler.test.ts | 55 +++++++++++++++++++ .../auth/external/ExternalTokenHandler.ts | 43 ++++++++++++--- .../auth/external/legacy.test.ts | 4 +- .../implementations/auth/external/legacy.ts | 33 ++++++----- .../auth/external/static.test.ts | 4 +- .../implementations/auth/external/static.ts | 45 ++++++--------- .../implementations/auth/external/types.ts | 2 +- 9 files changed, 138 insertions(+), 85 deletions(-) create mode 100644 packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts diff --git a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts index d0c11a4eee..b4feb46cce 100644 --- a/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts +++ b/packages/backend-app-api/src/services/implementations/auth/DefaultAuthService.ts @@ -23,11 +23,7 @@ import { BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; -import { - AuthenticationError, - ForwardedError, - NotAllowedError, -} from '@backstage/errors'; +import { AuthenticationError, ForwardedError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import { decodeJwt } from 'jose'; import { ExternalTokenHandler } from './external/ExternalTokenHandler'; @@ -86,20 +82,10 @@ export class DefaultAuthService implements AuthService { const externalResult = await this.externalTokenHandler.verifyToken(token); if (externalResult) { - const restrictions = externalResult.accessRestrictions; - if (restrictions) { - if (!restrictions.has(this.pluginId)) { - const valid = [...restrictions.keys()].map(k => `'${k}'`).join(', '); - throw new NotAllowedError( - `This token's access is restricted to plugin(s) ${valid}`, - ); - } - } - return createCredentialsWithServicePrincipal( externalResult.subject, undefined, - restrictions?.get(this.pluginId), + externalResult.accessRestrictions, ); } diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts index 7bbd012c01..ad4fb17d88 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts @@ -39,19 +39,7 @@ export const authServiceFactory = createServiceFactory({ // new auth services in the new backend system. tokenManager: coreServices.tokenManager, }, - async createRootContext({ config, logger }) { - const externalTokens = ExternalTokenHandler.create({ - config, - logger, - }); - return { - externalTokens, - }; - }, - async factory( - { config, discovery, plugin, tokenManager, logger, database }, - { externalTokens }, - ) { + async factory({ config, discovery, plugin, tokenManager, logger, database }) { const disableDefaultAuthPolicy = Boolean( config.getOptionalBoolean( 'backend.auth.dangerouslyDisableDefaultAuthPolicy', @@ -73,6 +61,11 @@ export const authServiceFactory = createServiceFactory({ publicKeyStore, discovery, }); + const externalTokens = ExternalTokenHandler.create({ + ownPluginId: plugin.getId(), + config, + logger, + }); return new DefaultAuthService( userTokens, diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts new file mode 100644 index 0000000000..1e82f76134 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; +import { ExternalTokenHandler } from './ExternalTokenHandler'; +import { TokenHandler } from './types'; + +describe('ExternalTokenHandler', () => { + it('skips over inner handlers that do not match, and applies plugin restrictions', async () => { + const handler1: TokenHandler = { + add: jest.fn(), + verifyToken: jest.fn().mockResolvedValue(undefined), + }; + + const handler2: TokenHandler = { + add: jest.fn(), + verifyToken: jest.fn().mockResolvedValue({ + subject: 'sub', + allAccessRestrictions: new Map( + Object.entries({ + plugin1: { + permissionNames: ['do.it'], + } satisfies BackstagePrincipalAccessRestrictions, + }), + ), + }), + }; + + const plugin1 = new ExternalTokenHandler('plugin1', [handler1, handler2]); + const plugin2 = new ExternalTokenHandler('plugin2', [handler1, handler2]); + + await expect(plugin1.verifyToken('token')).resolves.toEqual({ + subject: 'sub', + accessRestrictions: { permissionNames: ['do.it'] }, + }); + await expect( + plugin2.verifyToken('token'), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"This token's access is restricted to plugin(s) 'plugin1'"`, + ); + }); +}); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts index 82603953d0..72eeae6fcf 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.ts @@ -15,16 +15,19 @@ */ import { + BackstagePrincipalAccessRestrictions, LoggerService, RootConfigService, } from '@backstage/backend-plugin-api'; +import { NotAllowedError } from '@backstage/errors'; import { LegacyTokenHandler } from './legacy'; import { StaticTokenHandler } from './static'; import { JWKSHandler } from './jwks'; -import { AccessRestriptionsMap, TokenHandler } from './types'; +import { TokenHandler } from './types'; const NEW_CONFIG_KEY = 'backend.auth.externalAccess'; const OLD_CONFIG_KEY = 'backend.auth.keys'; +let loggedDeprecationWarning = false; /** * Handles all types of external caller token types (i.e. not Backstage user @@ -34,10 +37,11 @@ const OLD_CONFIG_KEY = 'backend.auth.keys'; */ export class ExternalTokenHandler { static create(options: { + ownPluginId: string; config: RootConfigService; logger: LoggerService; }): ExternalTokenHandler { - const { config, logger } = options; + const { ownPluginId, config, logger } = options; const staticHandler = new StaticTokenHandler(); const legacyHandler = new LegacyTokenHandler(); @@ -66,7 +70,8 @@ export class ExternalTokenHandler { // Load the old keys too const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? []; - if (legacyConfigs.length) { + if (legacyConfigs.length && !loggedDeprecationWarning) { + loggedDeprecationWarning = true; logger.warn( `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`, ); @@ -75,24 +80,48 @@ export class ExternalTokenHandler { legacyHandler.addOld(handlerConfig); } - return new ExternalTokenHandler(Object.values(handlers)); + return new ExternalTokenHandler(ownPluginId, Object.values(handlers)); } - constructor(private readonly handlers: TokenHandler[]) {} + constructor( + private readonly ownPluginId: string, + private readonly handlers: TokenHandler[], + ) {} async verifyToken(token: string): Promise< | { subject: string; - accessRestrictions?: AccessRestriptionsMap; + accessRestrictions?: BackstagePrincipalAccessRestrictions; } | undefined > { for (const handler of this.handlers) { const result = await handler.verifyToken(token); if (result) { - return result; + const { allAccessRestrictions, ...rest } = result; + if (allAccessRestrictions) { + const accessRestrictions = allAccessRestrictions.get( + this.ownPluginId, + ); + if (!accessRestrictions) { + const valid = [...allAccessRestrictions.keys()] + .map(k => `'${k}'`) + .join(', '); + throw new NotAllowedError( + `This token's access is restricted to plugin(s) ${valid}`, + ); + } + + return { + ...rest, + accessRestrictions, + }; + } + + return rest; } } + return undefined; } } diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts index 503157acc7..c674e46ec9 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.test.ts @@ -72,7 +72,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token1)).resolves.toEqual({ subject: 'key1', - accessRestrictions: accessRestrictions1, + allAccessRestrictions: accessRestrictions1, }); const token2 = await new SignJWT({ @@ -84,7 +84,7 @@ describe('LegacyTokenHandler', () => { await expect(tokenHandler.verifyToken(token2)).resolves.toEqual({ subject: 'key2', - accessRestrictions: accessRestrictions2, + allAccessRestrictions: accessRestrictions2, }); const token3 = await new SignJWT({ diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts index ba56d3a213..4eb982314b 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts @@ -27,16 +27,18 @@ import { AccessRestriptionsMap, TokenHandler } from './types'; export class LegacyTokenHandler implements TokenHandler { #entries = new Array<{ key: Uint8Array; - subject: string; - accessRestrictions?: AccessRestriptionsMap; + result: { + subject: string; + allAccessRestrictions?: AccessRestriptionsMap; + }; }>(); add(config: Config) { - const accessRestrictions = readAccessRestrictionsFromConfig(config); + const allAccessRestrictions = readAccessRestrictionsFromConfig(config); this.#doAdd( config.getString('options.secret'), config.getString('options.subject'), - accessRestrictions, + allAccessRestrictions, ); } @@ -49,10 +51,12 @@ export class LegacyTokenHandler implements TokenHandler { #doAdd( secret: string, subject: string, - accessRestrictions?: AccessRestriptionsMap, + allAccessRestrictions?: AccessRestriptionsMap, ) { if (!secret.match(/^\S+$/)) { throw new Error('Illegal secret, must be a valid base64 string'); + } else if (!subject.match(/^\S+$/)) { + throw new Error('Illegal subject, must be a set of non-space characters'); } let key: Uint8Array; @@ -62,14 +66,12 @@ export class LegacyTokenHandler implements TokenHandler { throw new Error('Illegal secret, must be a valid base64 string'); } - if (!subject.match(/^\S+$/)) { - throw new Error('Illegal subject, must be a set of non-space characters'); - } - this.#entries.push({ key, - subject, - accessRestrictions, + result: { + subject, + allAccessRestrictions, + }, }); } @@ -94,13 +96,10 @@ export class LegacyTokenHandler implements TokenHandler { return undefined; } - for (const entry of this.#entries) { + for (const { key, result } of this.#entries) { try { - await jwtVerify(token, entry.key); - return { - subject: entry.subject, - accessRestrictions: entry.accessRestrictions, - }; + await jwtVerify(token, key); + return result; } catch (e) { if (e.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') { throw e; diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts index 458c3f06f3..a5945daba9 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.test.ts @@ -45,11 +45,11 @@ describe('StaticTokenHandler', () => { await expect(handler.verifyToken('abcabcabc')).resolves.toEqual({ subject: 'one', - accessRestrictions: accessRestrictionsOne, + allAccessRestrictions: accessRestrictionsOne, }); await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ subject: 'two', - accessRestrictions: accessRestrictionsTwo, + allAccessRestrictions: accessRestrictionsTwo, }); await expect(handler.verifyToken('ghighighi')).resolves.toBeUndefined(); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/static.ts b/packages/backend-app-api/src/services/implementations/auth/external/static.ts index 8242b87dc0..1e89d8c6a1 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/static.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/static.ts @@ -26,46 +26,37 @@ const MIN_TOKEN_LENGTH = 8; * @internal */ export class StaticTokenHandler implements TokenHandler { - #entries = new Array<{ - token: string; - subject: string; - accessRestrictions?: AccessRestriptionsMap; - }>(); + #entries = new Map< + string, + { + subject: string; + allAccessRestrictions?: AccessRestriptionsMap; + } + >(); add(config: Config) { const token = config.getString('options.token'); + const subject = config.getString('options.subject'); + const allAccessRestrictions = readAccessRestrictionsFromConfig(config); + if (!token.match(/^\S+$/)) { throw new Error('Illegal token, must be a set of non-space characters'); - } - if (token.length < MIN_TOKEN_LENGTH) { + } else if (token.length < MIN_TOKEN_LENGTH) { throw new Error( `Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`, ); - } - - const subject = config.getString('options.subject'); - if (!subject.match(/^\S+$/)) { + } else if (!subject.match(/^\S+$/)) { throw new Error('Illegal subject, must be a set of non-space characters'); + } else if (this.#entries.has(token)) { + throw new Error( + 'Static externalAccess token was declared more than once', + ); } - const accessRestrictions = readAccessRestrictionsFromConfig(config); - - this.#entries.push({ - token, - subject, - accessRestrictions, - }); + this.#entries.set(token, { subject, allAccessRestrictions }); } async verifyToken(token: string) { - const entry = this.#entries.find(e => e.token === token); - if (!entry) { - return undefined; - } - - return { - subject: entry.subject, - accessRestrictions: entry.accessRestrictions, - }; + return this.#entries.get(token); } } diff --git a/packages/backend-app-api/src/services/implementations/auth/external/types.ts b/packages/backend-app-api/src/services/implementations/auth/external/types.ts index 54e51c1ef6..6a1fd084ed 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/types.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/types.ts @@ -27,7 +27,7 @@ export interface TokenHandler { verifyToken(token: string): Promise< | { subject: string; - accessRestrictions?: AccessRestriptionsMap; + allAccessRestrictions?: AccessRestriptionsMap; } | undefined >; From 0639b07aa1a0df026b35b24f07e00dc1cfd72da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 20 May 2024 15:47:49 +0200 Subject: [PATCH 518/567] refactor the client a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../implementations/auth/external/legacy.ts | 6 + .../src/ServerPermissionClient.test.ts | 283 ++++++++++++++---- .../src/ServerPermissionClient.ts | 135 ++++----- 3 files changed, 292 insertions(+), 132 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts index 4eb982314b..9c60e70707 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/legacy.ts @@ -66,6 +66,12 @@ export class LegacyTokenHandler implements TokenHandler { throw new Error('Illegal secret, must be a valid base64 string'); } + if (this.#entries.some(e => e.key === key)) { + throw new Error( + 'Legacy externalAccess token was declared more than once', + ); + } + this.#entries.push({ key, result: { diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 9becad4d7b..8273606845 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -366,63 +366,242 @@ describe('ServerPermissionClient', () => { }); describe('with access restrictions', () => { - it('short circuits the response when relevant access restrictions are present', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager(), - auth: mockServices.auth(), - }); - - // no restrictions for the given plugin - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', {}), - }), - ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); - - // matching permission name - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionNames: [testBasicPermission.name, 'other'], + it.each([{ enabled: true }, { enabled: false }])( + 'short circuits the response when using a service principal, applying the relevant access restrictions if present, when permissions %p', + async permissionConfig => { + const client = ServerPermissionClient.fromConfig( + new ConfigReader({ + permission: permissionConfig, }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + { + discovery, + tokenManager: mockServices.tokenManager(), + auth: mockServices.auth(), + }, + ); - // matching attributes - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionAttributes: { - action: [testBasicPermission.attributes.action!, 'other' as any], + // no restrictions for the given plugin + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', {}), }, - }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); - - // matching permission name but not attributes - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionNames: [testBasicPermission.name], - permissionAttributes: { - action: ['other' as any], + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', {}), }, - }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); - // matching attributes but not permission name - await expect( - client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service('foo', { - permissionNames: ['wrong-name'], - permissionAttributes: { - action: [testBasicPermission.attributes.action!], + // matching permission name + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission', 'other'], + }), }, - }), - }), - ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); - }); + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission', 'other'], + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching attributes + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionAttributes: { + action: ['create', 'other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionAttributes: { + action: ['create', 'other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.ALLOW }]); + + // matching permission name but not attributes + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission'], + permissionAttributes: { + action: ['other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['test.permission'], + permissionAttributes: { + action: ['other' as any], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + + // matching attributes but not permission name + await expect( + client.authorize( + [ + { + permission: createPermission({ + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['wrong-name'], + permissionAttributes: { + action: ['create'], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + await expect( + client.authorizeConditional( + [ + { + resourceRef: undefined as any, + permission: createPermission({ + resourceType: 'test', + name: 'test.permission', + attributes: { + action: 'create', + }, + }), + }, + ], + { + credentials: mockCredentials.service('foo', { + permissionNames: ['wrong-name'], + permissionAttributes: { + action: ['create'], + }, + }), + }, + ), + ).resolves.toEqual([{ result: AuthorizeResult.DENY }]); + }, + ); }); }); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index 6e3901d65b..cb39f310bd 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -21,26 +21,27 @@ import { import { AuthService, BackstageCredentials, + BackstageServicePrincipal, DiscoveryService, PermissionsService, PermissionsServiceRequestOptions, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { - AuthorizeResult, - PermissionClient, AuthorizePermissionRequest, AuthorizePermissionResponse, + AuthorizeResult, + DefinitivePolicyDecision, + Permission, + PermissionClient, PolicyDecision, QueryPermissionRequest, - DefinitivePolicyDecision, } from '@backstage/plugin-permission-common'; /** * A thin wrapper around - * {@link @backstage/plugin-permission-common#PermissionClient} that ensures the - * proper short-circuit handling of service principals. - * + * {@link @backstage/plugin-permission-common#PermissionClient} that allows all + * service-to-service requests. * @public */ export class ServerPermissionClient implements PermissionsService { @@ -93,47 +94,39 @@ export class ServerPermissionClient implements PermissionsService { queries: QueryPermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { - const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + const credentials = await this.#getIncomingCredentials(options); + if (credentials && this.#auth.isPrincipal(credentials, 'service')) { + return this.#servicePrincipalDecision(queries, credentials); + } else if (!this.#permissionEnabled) { + return queries.map(_ => ({ result: AuthorizeResult.ALLOW })); + } + + return this.#permissionClient.authorizeConditional( queries, - options, + await this.#getRequestOptions(options), ); - if (maybeResponse) { - return maybeResponse; - } - - if (await this.#shouldPermissionsBeApplied(options)) { - return this.#permissionClient.authorizeConditional( - queries, - await this.#getRequestOptions(options), - ); - } - - return queries.map(_ => ({ result: AuthorizeResult.ALLOW })); } async authorize( requests: AuthorizePermissionRequest[], options?: PermissionsServiceRequestOptions, ): Promise { - const maybeResponse = this.#decideBasedOnPrincipalAccessRestrictions( + const credentials = await this.#getIncomingCredentials(options); + if (credentials && this.#auth.isPrincipal(credentials, 'service')) { + return this.#servicePrincipalDecision(requests, credentials); + } else if (!this.#permissionEnabled) { + return requests.map(_ => ({ result: AuthorizeResult.ALLOW })); + } + + return this.#permissionClient.authorize( requests, - options, + await this.#getRequestOptions(options), ); - if (maybeResponse) { - return maybeResponse; - } - - if (await this.#shouldPermissionsBeApplied(options)) { - return this.#permissionClient.authorize( - requests, - await this.#getRequestOptions(options), - ); - } - - return requests.map(_ => ({ result: AuthorizeResult.ALLOW })); } - async #getRequestOptions(options?: PermissionsServiceRequestOptions) { + async #getRequestOptions( + options?: PermissionsServiceRequestOptions, + ): Promise<{ token?: string } | undefined> { if (options && 'credentials' in options) { if (this.#auth.isPrincipal(options.credentials, 'none')) { return {}; @@ -148,66 +141,48 @@ export class ServerPermissionClient implements PermissionsService { return options; } - #decideBasedOnPrincipalAccessRestrictions( - requests: Array, + async #getIncomingCredentials( options?: PermissionsServiceRequestOptions, - ): DefinitivePolicyDecision[] | undefined { - if (!options || !('credentials' in options)) { - return undefined; + ): Promise { + if (options && 'credentials' in options) { + return options.credentials; } - // Bail out to the old behavior if - // - the principal is not a service - // - the principal was apparently unrestricted - const credentials = options.credentials; - if ( - !this.#auth.isPrincipal(credentials, 'service') || - !credentials.principal.accessRestrictions - ) { - return undefined; + if (options?.token) { + try { + return await this.#auth.authenticate(options.token); + } catch { + // ignore + } } + return undefined; + } + + /** + * For service principals, we can always make an immediate definitive decision + * based on their associated access restrictions (if any). + */ + #servicePrincipalDecision( + input: { permission: Permission }[], + credentials: BackstageCredentials, + ): DefinitivePolicyDecision[] { const { permissionNames, permissionAttributes } = - credentials.principal.accessRestrictions; + credentials.principal.accessRestrictions ?? {}; - return requests.map(query => { - if (permissionNames && !permissionNames.includes(query.permission.name)) { + return input.map(item => { + if (permissionNames && !permissionNames.includes(item.permission.name)) { return { result: AuthorizeResult.DENY }; } + if (permissionAttributes?.action) { - const action = query.permission.attributes?.action; + const action = item.permission.attributes?.action; if (!action || !permissionAttributes.action.includes(action)) { return { result: AuthorizeResult.DENY }; } } + return { result: AuthorizeResult.ALLOW }; }); } - - async #shouldPermissionsBeApplied( - options?: PermissionsServiceRequestOptions, - ) { - if (!this.#permissionEnabled) { - return false; - } - - let credentials: BackstageCredentials; - if (options && 'credentials' in options) { - credentials = options.credentials; - } else { - if (!options?.token) { - return true; - } - try { - credentials = await this.#auth.authenticate(options.token); - } catch { - return true; - } - } - - if (this.#auth.isPrincipal(credentials, 'service')) { - return false; - } - return true; - } } From c2ea75f4733e6f9bb3d9c8754aad405c3b4a3cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 22 May 2024 16:40:53 +0200 Subject: [PATCH 519/567] update the jwks external auth to use singular nouns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/auth/service-to-service-auth.md | 18 ++--- packages/backend-app-api/config.d.ts | 20 +++--- .../auth/external/helpers.test.ts | 68 ++++++++++++++++++- .../implementations/auth/external/helpers.ts | 11 ++- .../auth/external/jwks.test.ts | 34 +++++----- .../implementations/auth/external/jwks.ts | 7 +- 6 files changed, 112 insertions(+), 46 deletions(-) diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index a728bf064b..ee88e45305 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -96,29 +96,25 @@ backend: - type: jwks options: url: https://example.com/.well-known/jwks.json - issuers: - - https://example.com - algorithms: - - RS256 - audiences: - - example + issuer: https://example.com + algorithm: RS256 + audience: example, other-example subjectPrefix: custom-prefix - type: jwks options: url: https://another-example.com/.well-known/jwks.json - issuers: - - https://example.com + issuer: https://example.com ``` The URL should point at an unauthenticated endpoint that returns the JWKS. -Issuers specifies the issuer(s) of the JWT that the authenticating app will accept. +`issuer` specifies the issuer(s) of the JWT that the authenticating app will accept. Passed JWTs must have an `iss` claim which matches one of the specified issuers. -Algorithms specifies the algorithm(s) that are used to verify the JWT. The passed JWTs +`algorithm` specifies the algorithm(s) that are used to verify the JWT. The passed JWTs must have been signed using one of the listed algorithms. -Audiences specify the intended audience(s) of the JWT. The passed JWTs must have an "aud" +`audience` specifies the intended audience(s) of the JWT. The passed JWTs must have an "aud" claim that matches one of the audiences specified, or have no audience specified. For additional details regarding the JWKS configuration, please consult your authentication diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index 5b72fef54c..80f97cb4b5 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -226,30 +226,30 @@ export interface Config { type: 'jwks'; options: { /** - * Sets the algorithms that should be used to verify the JWT tokens. + * The full URL of the JWKS endpoint. + */ + url: string; + /** + * Sets the algorithm(s) that should be used to verify the JWT tokens. * The passed JWTs must have been signed using one of the listed algorithms. */ - algorithms?: string[]; + algorithm?: string | string[]; /** - * Sets the issuers that should be used to verify the JWT tokens. + * Sets the issuer(s) that should be used to verify the JWT tokens. * Passed JWTs must have an `iss` claim which matches one of the specified issuers. */ - issuers?: string[]; + issuer?: string | string[]; /** - * Sets the audiences that should be used to verify the JWT tokens. + * Sets the audience(s) that should be used to verify the JWT tokens. * The passed JWTs must have an "aud" claim that matches one of the audiences specified, * or have no audience specified. */ - audiences?: string[]; + audience?: string | string[]; /** * Sets an optional subject prefix. Passes the subject to called plugins. * Useful for debugging and tracking purposes. */ subjectPrefix?: string; - /** - * Sets the URL containing the JWKS endpoint. - */ - url: string; }; } >; diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts index ee0dc3cb87..5b8e40ed4f 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.test.ts @@ -15,8 +15,74 @@ */ import { ConfigReader } from '@backstage/config'; -import { readAccessRestrictionsFromConfig } from './helpers'; +import { + readAccessRestrictionsFromConfig, + readStringOrStringArrayFromConfig, +} from './helpers'; import { JsonObject } from '@backstage/types'; +import { mockServices } from '@backstage/backend-test-utils'; + +describe('readStringOrStringArrayFromConfig', () => { + it('handles all cases correctly', () => { + const config = mockServices.rootConfig({ + data: { + wrongType: 1, + wrongTypeInArray: [1], + singleString: 'a', + spaceSeparatedString: 'a b c', + commaSeparatedString: 'a,b,c', + mixedSeparatorsString: 'a b,c ,, d', + emptyString: '', + emptyArray: [], + simpleArray: ['a', 'b', 'c'], + arrayWithSeparators: ['a b', 'c,d', 'e'], + complexDuplicates: ['a', 'a b', 'a', 'b, a'], + }, + }); + + expect(() => + readStringOrStringArrayFromConfig(config, 'wrongType'), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'wrongType' in 'mock-config', got number, wanted string"`, + ); + expect(() => + readStringOrStringArrayFromConfig(config, 'wrongTypeInArray'), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'wrongTypeInArray[0]' in 'mock-config', got number, wanted string-array"`, + ); + expect(readStringOrStringArrayFromConfig(config, 'singleString')).toEqual([ + 'a', + ]); + expect( + readStringOrStringArrayFromConfig(config, 'spaceSeparatedString'), + ).toEqual(['a', 'b', 'c']); + expect( + readStringOrStringArrayFromConfig(config, 'commaSeparatedString'), + ).toEqual(['a', 'b', 'c']); + expect( + readStringOrStringArrayFromConfig(config, 'mixedSeparatorsString'), + ).toEqual(['a', 'b', 'c', 'd']); + expect(() => + readStringOrStringArrayFromConfig(config, 'emptyString'), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid type in config for key 'emptyString' in 'mock-config', got empty-string, wanted string"`, + ); + expect( + readStringOrStringArrayFromConfig(config, 'emptyArray'), + ).toBeUndefined(); + expect(readStringOrStringArrayFromConfig(config, 'simpleArray')).toEqual([ + 'a', + 'b', + 'c', + ]); + expect( + readStringOrStringArrayFromConfig(config, 'arrayWithSeparators'), + ).toEqual(['a', 'b', 'c', 'd', 'e']); + expect( + readStringOrStringArrayFromConfig(config, 'complexDuplicates'), + ).toEqual(['a', 'b']); + }); +}); describe('readAccessRestrictionsFromConfig', () => { function r(config: JsonObject) { diff --git a/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts index 5d199a5067..d6aa0a01ff 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/helpers.ts @@ -66,8 +66,10 @@ export function readAccessRestrictionsFromConfig( * splits by comma/space into a string array. Can also validate against a known * set of values. Returns undefined if the key didn't exist or if the array * would have ended up being empty. + * + * @internal */ -function stringOrStringArray( +export function readStringOrStringArrayFromConfig( root: Config, key: string, validValues?: readonly T[], @@ -108,7 +110,10 @@ function stringOrStringArray( } function readPermissionNames(externalAccessEntryConfig: Config) { - return stringOrStringArray(externalAccessEntryConfig, 'permission'); + return readStringOrStringArrayFromConfig( + externalAccessEntryConfig, + 'permission', + ); } function readPermissionAttributes(externalAccessEntryConfig: Config) { @@ -129,7 +134,7 @@ function readPermissionAttributes(externalAccessEntryConfig: Config) { } } - const action = stringOrStringArray(config, 'action', [ + const action = readStringOrStringArrayFromConfig(config, 'action', [ 'create', 'read', 'update', diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index 4cbdcb1cb0..0466cdf034 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -101,9 +101,9 @@ describe('JWKSHandler', () => { it('verifies token with valid entry', async () => { const validEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: [mockBaseUrl], - audiences: ['backstage'], + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', }; const jwksHandler = new JWKSHandler(); @@ -121,16 +121,16 @@ describe('JWKSHandler', () => { it('skips invalid entry and continues verification', async () => { const invalidEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: ['fakeIssuer'], - audiences: ['fakeAud'], + algorithm: 'RS256', + issuer: ['fakeIssuer'], + audience: ['fakeAud'], }; const validEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: ['multiple-issuers', mockBaseUrl], - audiences: ['multiple-audiences', 'backstage'], + algorithm: 'RS256', + issuer: ['multiple-issuers', mockBaseUrl], + audience: ['multiple-audiences', 'backstage'], }; const jwksHandler = new JWKSHandler(); @@ -149,16 +149,14 @@ describe('JWKSHandler', () => { it('returns undefined if no valid entry found', async () => { const invalidEntry1 = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: [mockBaseUrl], - audiences: [], + algorithm: 'RS256', + issuer: 'wrong', }; const invalidEntry2 = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['HS256'], - issuers: [], - audiences: ['backstage'], + algorithm: ['HS256'], + audience: 'wrong', }; const jwksHandler = new JWKSHandler(); @@ -201,9 +199,9 @@ describe('JWKSHandler', () => { it('uses custom subject prefix if provided', async () => { const validEntry = { url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithms: ['RS256'], - issuers: [mockBaseUrl], - audiences: ['backstage'], + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', subjectPrefix: 'custom-prefix', }; const jwksHandler = new JWKSHandler(); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index ea62b3880c..8af44f4d54 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -16,6 +16,7 @@ import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose'; import { Config } from '@backstage/config'; +import { readStringOrStringArrayFromConfig } from './helpers'; import { TokenHandler } from './types'; /** @@ -34,9 +35,9 @@ export class JWKSHandler implements TokenHandler { }> = []; add(options: Config) { - const algorithms = options.getOptionalStringArray('algorithms'); - const issuers = options.getOptionalStringArray('issuers'); - const audiences = options.getOptionalStringArray('audiences'); + const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm'); + const issuers = readStringOrStringArrayFromConfig(options, 'issuer'); + const audiences = readStringOrStringArrayFromConfig(options, 'audience'); const subjectPrefix = options.getOptionalString('subjectPrefix'); const url = new URL(options.getString('url')); const jwks = createRemoteJWKSet(url); From 805cbe7970854ff698b5ab49aaba7a0cdc9bc21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 22 May 2024 14:43:20 +0200 Subject: [PATCH 520/567] add cache testing utilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/cyan-jobs-visit.md | 5 + .github/workflows/ci.yml | 4 +- .github/workflows/deploy_packages.yml | 10 + ...tegration.test.ts => CacheManager.test.ts} | 63 +++--- packages/backend-test-utils/api-report.md | 23 ++ packages/backend-test-utils/package.json | 4 + .../src/cache/TestCaches.test.ts | 53 +++++ .../src/cache/TestCaches.ts | 210 ++++++++++++++++++ .../backend-test-utils/src/cache/index.ts | 18 ++ .../src/cache/memcache.test.ts | 34 +++ .../backend-test-utils/src/cache/memcache.ts | 83 +++++++ .../src/cache/redis.test.ts | 34 +++ .../backend-test-utils/src/cache/redis.ts | 81 +++++++ .../backend-test-utils/src/cache/types.ts | 61 +++++ .../backend-test-utils/src/database/index.ts | 1 - packages/backend-test-utils/src/index.ts | 1 + .../src/util/isDockerDisabledForTests.ts | 2 +- yarn.lock | 33 ++- 18 files changed, 678 insertions(+), 42 deletions(-) create mode 100644 .changeset/cyan-jobs-visit.md rename packages/backend-defaults/src/entrypoints/cache/{CacheManager.integration.test.ts => CacheManager.test.ts} (61%) create mode 100644 packages/backend-test-utils/src/cache/TestCaches.test.ts create mode 100644 packages/backend-test-utils/src/cache/TestCaches.ts create mode 100644 packages/backend-test-utils/src/cache/index.ts create mode 100644 packages/backend-test-utils/src/cache/memcache.test.ts create mode 100644 packages/backend-test-utils/src/cache/memcache.ts create mode 100644 packages/backend-test-utils/src/cache/redis.test.ts create mode 100644 packages/backend-test-utils/src/cache/redis.ts create mode 100644 packages/backend-test-utils/src/cache/types.ts diff --git a/.changeset/cyan-jobs-visit.md b/.changeset/cyan-jobs-visit.md new file mode 100644 index 0000000000..ccc8c90adf --- /dev/null +++ b/.changeset/cyan-jobs-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-test-utils': minor +--- + +Added `TestCaches` that functions just like `TestDatabases` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45d2363a36..fd3b66ddaa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,7 +188,7 @@ jobs: ports: - 3306/tcp redis: - image: redis + image: redis:7 options: >- --health-cmd "redis-cli ping" --health-interval 10s @@ -240,7 +240,7 @@ jobs: BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored - BACKSTAGE_TEST_CACHE_REDIS_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }} + BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }} # We run the test cases before verifying the specs to prevent any failing tests from causing errors. - name: verify openapi specs against test cases diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 444f3f825d..cd2947a565 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -55,6 +55,15 @@ jobs: --health-retries 5 ports: - 3306/tcp + redis: + image: redis:7 + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379/tcp env: CI: true @@ -115,6 +124,7 @@ jobs: BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored + BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING: redis://localhost:${{ job.services.redis.ports[6379] }} - name: Discord notification if: ${{ failure() }} diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts similarity index 61% rename from packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts rename to packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts index a10b02a62e..6eb907764d 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.integration.test.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { mockServices } from '@backstage/backend-test-utils'; +import { mockServices, TestCaches } from '@backstage/backend-test-utils'; import KeyvRedis from '@keyv/redis'; +import KeyvMemcache from '@keyv/memcache'; import { CacheManager } from './CacheManager'; // This test is in a separate file because the main test file uses other mocking @@ -23,28 +24,32 @@ import { CacheManager } from './CacheManager'; // Contrived code because it's hard to spy on a default export jest.mock('@keyv/redis', () => { - const ActualKeyvRedis = jest.requireActual('@keyv/redis'); + const Actual = jest.requireActual('@keyv/redis'); return jest.fn((...args: any[]) => { - return new ActualKeyvRedis(...args); + return new Actual(...args); + }); +}); +jest.mock('@keyv/memcache', () => { + const Actual = jest.requireActual('@keyv/memcache'); + return jest.fn((...args: any[]) => { + return new Actual(...args); }); }); describe('CacheManager integration', () => { - describe('redis', () => { - it('only creates one underlying connection', async () => { - const connection = - process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; - if (!connection) { - return; - } + const caches = TestCaches.create(); + + afterEach(jest.clearAllMocks); + + it.each(caches.eachSupportedId())( + 'only creates one underlying connection, %p', + async cacheId => { + const { store, connection } = await caches.init(cacheId); const manager = CacheManager.fromConfig( mockServices.rootConfig({ - data: { - backend: { cache: { store: 'redis', connection } }, - }, + data: { backend: { cache: { store, connection } } }, }), - { onError: e => expect(e).not.toBeDefined() }, ); manager.forPlugin('p1').getClient(); @@ -52,25 +57,27 @@ describe('CacheManager integration', () => { manager.forPlugin('p2').getClient(); manager.forPlugin('p3').getClient({}); - expect(KeyvRedis).toHaveBeenCalledTimes(1); - }); - - it('interacts correctly with redis', async () => { - // TODO(freben): This could be frameworkified as TestCaches just like - // TestDatabases, but that will have to come some other day - const connection = - process.env.BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING; - if (!connection) { - return; + if (store === 'redis') { + // eslint-disable-next-line jest/no-conditional-expect + expect(KeyvRedis).toHaveBeenCalledTimes(1); + } else if (store === 'memcache') { + // eslint-disable-next-line jest/no-conditional-expect + expect(KeyvMemcache).toHaveBeenCalledTimes(1); } + }, + ); + + it.each(caches.eachSupportedId())( + 'interacts correctly with store, %p', + async cacheId => { + const { store, connection } = await caches.init(cacheId); const manager = CacheManager.fromConfig( mockServices.rootConfig({ data: { - backend: { cache: { store: 'redis', connection } }, + backend: { cache: { store, connection } }, }, }), - { onError: e => expect(e).not.toBeDefined() }, ); const plugin1 = manager.forPlugin('p1').getClient(); @@ -84,6 +91,6 @@ describe('CacheManager integration', () => { await expect(plugin1.get('a')).resolves.toBe('plugin1'); await expect(plugin2a.get('a')).resolves.toBe('plugin2b'); await expect(plugin2b.get('a')).resolves.toBe('plugin2b'); - }); - }); + }, + ); }); diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 4f6eb47243..21bbcc4e87 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -25,6 +25,7 @@ import { HttpRouterFactoryOptions } from '@backstage/backend-app-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; +import Keyv from 'keyv'; import { Knex } from 'knex'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; @@ -423,6 +424,28 @@ export interface TestBackendOptions { >; } +// @public +export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1'; + +// @public +export class TestCaches { + static create(options?: { + ids?: TestCacheId[]; + disableDocker?: boolean; + }): TestCaches; + // (undocumented) + eachSupportedId(): [TestCacheId][]; + init(id: TestCacheId): Promise<{ + store: string; + connection: string; + keyv: Keyv; + }>; + // (undocumented) + static setDefaults(options: { ids?: TestCacheId[] }): void; + // (undocumented) + supports(id: TestCacheId): boolean; +} + // @public export type TestDatabaseId = | 'POSTGRES_16' diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 00d5b3b4e4..c84956e58a 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -53,10 +53,14 @@ "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/types": "workspace:^", + "@keyv/memcache": "^1.3.5", + "@keyv/redis": "^2.5.3", + "@types/keyv": "^4.2.0", "better-sqlite3": "^9.0.0", "cookie": "^0.6.0", "express": "^4.17.1", "fs-extra": "^11.0.0", + "keyv": "^4.5.2", "knex": "^3.0.0", "msw": "^1.0.0", "mysql2": "^3.0.0", diff --git a/packages/backend-test-utils/src/cache/TestCaches.test.ts b/packages/backend-test-utils/src/cache/TestCaches.test.ts new file mode 100644 index 0000000000..7d5ef69704 --- /dev/null +++ b/packages/backend-test-utils/src/cache/TestCaches.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { isDockerDisabledForTests } from '../util'; +import { TestCaches } from './TestCaches'; + +const itIfDocker = isDockerDisabledForTests() ? it.skip : it; + +jest.setTimeout(60_000); + +describe('TestCaches', () => { + const caches = TestCaches.create(); + + it.each(caches.eachSupportedId())('fires up a cache, %p', async cacheId => { + const { keyv } = await caches.init(cacheId); + await keyv.set('test', 'value'); + await expect(keyv.get('test')).resolves.toBe('value'); + }); + + itIfDocker('clears between tests, part 1', async () => { + const { keyv } = await caches.init('REDIS_7'); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('collision')).resolves.toBeUndefined(); + await keyv.set('collision', 'something'); + }); + + itIfDocker('clears between tests, part 2', async () => { + const { keyv } = await caches.init('REDIS_7'); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('collision')).resolves.toBeUndefined(); + await keyv.set('collision', 'something'); + }); + + itIfDocker('clears between tests, part 3', async () => { + const { keyv } = await caches.init('REDIS_7'); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('collision')).resolves.toBeUndefined(); + await keyv.set('collision', 'something'); + }); +}); diff --git a/packages/backend-test-utils/src/cache/TestCaches.ts b/packages/backend-test-utils/src/cache/TestCaches.ts new file mode 100644 index 0000000000..e9ba0e5c4f --- /dev/null +++ b/packages/backend-test-utils/src/cache/TestCaches.ts @@ -0,0 +1,210 @@ +/* + * 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 Keyv from 'keyv'; +import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; +import { connectToExternalMemcache, startMemcachedContainer } from './memcache'; +import { connectToExternalRedis, startRedisContainer } from './redis'; +import { Instance, TestCacheId, TestCacheProperties, allCaches } from './types'; + +/** + * Encapsulates the creation of ephemeral test cache instances for use inside + * unit or integration tests. + * + * @public + */ +export class TestCaches { + private readonly instanceById: Map; + private readonly supportedIds: TestCacheId[]; + private static defaultIds?: TestCacheId[]; + + /** + * Creates an empty `TestCaches` instance, and sets up Jest to clean up all of + * its acquired resources after all tests finish. + * + * You typically want to create just a single instance like this at the top of + * your test file or `describe` block, and then call `init` many times on that + * instance inside the individual tests. Spinning up a "physical" cache + * instance takes a considerable amount of time, slowing down tests. But + * wiping the contents of an instance using `init` is very fast. + */ + static create(options?: { + ids?: TestCacheId[]; + disableDocker?: boolean; + }): TestCaches { + const ids = options?.ids; + const disableDocker = options?.disableDocker ?? isDockerDisabledForTests(); + + let testCacheIds: TestCacheId[]; + if (ids) { + testCacheIds = ids; + } else if (TestCaches.defaultIds) { + testCacheIds = TestCaches.defaultIds; + } else { + testCacheIds = Object.keys(allCaches) as TestCacheId[]; + } + + const supportedIds = testCacheIds.filter(id => { + const properties = allCaches[id]; + if (!properties) { + return false; + } + // If the caller has set up the env with an explicit connection string, + // we'll assume that this target will work + if ( + properties.connectionStringEnvironmentVariableName && + process.env[properties.connectionStringEnvironmentVariableName] + ) { + return true; + } + // If the cache doesn't require docker at all, there's nothing to worry + // about + if (!properties.dockerImageName) { + return true; + } + // If the cache requires docker, but docker is disabled, we will fail. + if (disableDocker) { + return false; + } + return true; + }); + + const caches = new TestCaches(supportedIds); + + if (supportedIds.length > 0) { + afterAll(async () => { + await caches.shutdown(); + }); + } + + return caches; + } + + static setDefaults(options: { ids?: TestCacheId[] }) { + TestCaches.defaultIds = options.ids; + } + + private constructor(supportedIds: TestCacheId[]) { + this.instanceById = new Map(); + this.supportedIds = supportedIds; + } + + supports(id: TestCacheId): boolean { + return this.supportedIds.includes(id); + } + + eachSupportedId(): [TestCacheId][] { + return this.supportedIds.map(id => [id]); + } + + /** + * Returns a fresh, empty cache for the given driver. + * + * @param id - The ID of the cache to use, e.g. 'REDIS_7' + * @returns Cache connection properties + */ + async init( + id: TestCacheId, + ): Promise<{ store: string; connection: string; keyv: Keyv }> { + const properties = allCaches[id]; + if (!properties) { + const candidates = Object.keys(allCaches).join(', '); + throw new Error( + `Unknown test cache ${id}, possible values are ${candidates}`, + ); + } + if (!this.supportedIds.includes(id)) { + const candidates = this.supportedIds.join(', '); + throw new Error( + `Unsupported test cache ${id} for this environment, possible values are ${candidates}`, + ); + } + + // Ensure that a testcontainers instance is up for this ID + let instance: Instance | undefined = this.instanceById.get(id); + if (!instance) { + instance = await this.initAny(properties); + this.instanceById.set(id, instance); + } + + // Ensure that it's cleared of data from previous tests + await instance.keyv.clear(); + + return { + store: instance.store, + connection: instance.connection, + keyv: instance.keyv, + }; + } + + private async initAny(properties: TestCacheProperties): Promise { + switch (properties.store) { + case 'memcache': + return this.initMemcached(properties); + case 'redis': + return this.initRedis(properties); + case 'memory': + return { + store: 'memory', + connection: 'memory', + keyv: new Keyv(), + stop: async () => {}, + }; + default: + throw new Error(`Unknown cache store '${properties.store}'`); + } + } + + private async initMemcached( + properties: TestCacheProperties, + ): Promise { + // Use the connection string if provided + const envVarName = properties.connectionStringEnvironmentVariableName; + if (envVarName) { + const connectionString = process.env[envVarName]; + if (connectionString) { + return connectToExternalMemcache(connectionString); + } + } + + return await startMemcachedContainer(properties.dockerImageName!); + } + + private async initRedis(properties: TestCacheProperties): Promise { + // Use the connection string if provided + const envVarName = properties.connectionStringEnvironmentVariableName; + if (envVarName) { + const connectionString = process.env[envVarName]; + if (connectionString) { + return connectToExternalRedis(connectionString); + } + } + + return await startRedisContainer(properties.dockerImageName!); + } + + private async shutdown() { + const instances = [...this.instanceById.values()]; + this.instanceById.clear(); + await Promise.all( + instances.map(({ stop }) => + stop().catch(error => { + console.warn(`TestCaches: Failed to stop container`, { error }); + }), + ), + ); + } +} diff --git a/packages/backend-test-utils/src/cache/index.ts b/packages/backend-test-utils/src/cache/index.ts new file mode 100644 index 0000000000..46d95545bf --- /dev/null +++ b/packages/backend-test-utils/src/cache/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { TestCaches } from './TestCaches'; +export type { TestCacheId } from './types'; diff --git a/packages/backend-test-utils/src/cache/memcache.test.ts b/packages/backend-test-utils/src/cache/memcache.test.ts new file mode 100644 index 0000000000..bd912ce9d2 --- /dev/null +++ b/packages/backend-test-utils/src/cache/memcache.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; +import { startMemcachedContainer } from './memcache'; +import { v4 as uuid } from 'uuid'; + +const itIfDocker = isDockerDisabledForTests() ? it.skip : it; + +jest.setTimeout(60_000); + +describe('startMemcachedContainer', () => { + itIfDocker('successfully launches the container', async () => { + const { stop, keyv } = await startMemcachedContainer('memcached:1'); + const value = uuid(); + await keyv.set('test', value); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('test')).resolves.toBe(value); + await stop(); + }); +}); diff --git a/packages/backend-test-utils/src/cache/memcache.ts b/packages/backend-test-utils/src/cache/memcache.ts new file mode 100644 index 0000000000..b7ac07cb13 --- /dev/null +++ b/packages/backend-test-utils/src/cache/memcache.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Keyv from 'keyv'; +import KeyvMemcache from '@keyv/memcache'; +import { v4 as uuid } from 'uuid'; +import { Instance } from './types'; + +async function attemptMemcachedConnection(connection: string): Promise { + const startTime = Date.now(); + + for (;;) { + try { + const store = new KeyvMemcache(connection); + const keyv = new Keyv({ store }); + const value = uuid(); + await keyv.set('test', value); + if ((await keyv.get('test')) === value) { + return keyv; + } + } catch (e) { + if (Date.now() - startTime > 30_000) { + throw new Error( + `Timed out waiting for memcached to be ready for connections, ${e}`, + ); + } + } + + await new Promise(resolve => setTimeout(resolve, 100)); + } +} + +export async function connectToExternalMemcache( + connection: string, +): Promise { + const keyv = await attemptMemcachedConnection(connection); + return { + store: 'memcache', + connection, + keyv, + stop: async () => await keyv.disconnect(), + }; +} + +export async function startMemcachedContainer( + image: string, +): Promise { + // Lazy-load to avoid side-effect of importing testcontainers + const { GenericContainer } = await import('testcontainers'); + + const container = await new GenericContainer(image) + .withExposedPorts(11211) + .start(); + + const host = container.getHost(); + const port = container.getMappedPort(11211); + const connection = `${host}:${port}`; + + const keyv = await attemptMemcachedConnection(connection); + + return { + store: 'memcache', + connection, + keyv, + stop: async () => { + await keyv.disconnect(); + await container.stop({ timeout: 10_000 }); + }, + }; +} diff --git a/packages/backend-test-utils/src/cache/redis.test.ts b/packages/backend-test-utils/src/cache/redis.test.ts new file mode 100644 index 0000000000..6555a26677 --- /dev/null +++ b/packages/backend-test-utils/src/cache/redis.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; +import { startRedisContainer } from './redis'; +import { v4 as uuid } from 'uuid'; + +const itIfDocker = isDockerDisabledForTests() ? it.skip : it; + +jest.setTimeout(60_000); + +describe('startRedisContainer', () => { + itIfDocker('successfully launches the container', async () => { + const { stop, keyv } = await startRedisContainer('redis:7'); + const value = uuid(); + await keyv.set('test', value); + // eslint-disable-next-line jest/no-standalone-expect + await expect(keyv.get('test')).resolves.toBe(value); + await stop(); + }); +}); diff --git a/packages/backend-test-utils/src/cache/redis.ts b/packages/backend-test-utils/src/cache/redis.ts new file mode 100644 index 0000000000..6185e4d076 --- /dev/null +++ b/packages/backend-test-utils/src/cache/redis.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Keyv from 'keyv'; +import KeyvRedis from '@keyv/redis'; +import { v4 as uuid } from 'uuid'; +import { Instance } from './types'; + +async function attemptRedisConnection(connection: string): Promise { + const startTime = Date.now(); + + for (;;) { + try { + const store = new KeyvRedis(connection); + const keyv = new Keyv({ store }); + const value = uuid(); + await keyv.set('test', value); + if ((await keyv.get('test')) === value) { + return keyv; + } + } catch (e) { + if (Date.now() - startTime > 30_000) { + throw new Error( + `Timed out waiting for redis to be ready for connections, ${e}`, + ); + } + } + + await new Promise(resolve => setTimeout(resolve, 100)); + } +} + +export async function connectToExternalRedis( + connection: string, +): Promise { + const keyv = await attemptRedisConnection(connection); + return { + store: 'redis', + connection, + keyv, + stop: async () => await keyv.disconnect(), + }; +} + +export async function startRedisContainer(image: string): Promise { + // Lazy-load to avoid side-effect of importing testcontainers + const { GenericContainer } = await import('testcontainers'); + + const container = await new GenericContainer(image) + .withExposedPorts(6379) + .start(); + + const host = container.getHost(); + const port = container.getMappedPort(6379); + const connection = `redis://${host}:${port}`; + + const keyv = await attemptRedisConnection(connection); + + return { + store: 'redis', + connection, + keyv, + stop: async () => { + await keyv.disconnect(); + await container.stop({ timeout: 10_000 }); + }, + }; +} diff --git a/packages/backend-test-utils/src/cache/types.ts b/packages/backend-test-utils/src/cache/types.ts new file mode 100644 index 0000000000..1aea522042 --- /dev/null +++ b/packages/backend-test-utils/src/cache/types.ts @@ -0,0 +1,61 @@ +/* + * 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 Keyv from 'keyv'; +import { getDockerImageForName } from '../util/getDockerImageForName'; + +/** + * The possible caches to test against. + * + * @public + */ +export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1'; + +export type TestCacheProperties = { + name: string; + store: string; + dockerImageName?: string; + connectionStringEnvironmentVariableName?: string; +}; + +export type Instance = { + store: string; + connection: string; + keyv: Keyv; + stop: () => Promise; +}; + +export const allCaches: Record = + Object.freeze({ + REDIS_7: { + name: 'Redis 7.x', + store: 'redis', + dockerImageName: getDockerImageForName('redis:7'), + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_CACHE_REDIS7_CONNECTION_STRING', + }, + MEMCACHED_1: { + name: 'Memcached 1.x', + store: 'memcache', + dockerImageName: getDockerImageForName('memcached:1'), + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_CACHE_MEMCACHED1_CONNECTION_STRING', + }, + MEMORY: { + name: 'In-memory', + store: 'memory', + }, + }); diff --git a/packages/backend-test-utils/src/database/index.ts b/packages/backend-test-utils/src/database/index.ts index 69e3f41452..949553cef2 100644 --- a/packages/backend-test-utils/src/database/index.ts +++ b/packages/backend-test-utils/src/database/index.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export { isDockerDisabledForTests } from '../util/isDockerDisabledForTests'; export { TestDatabases } from './TestDatabases'; export type { TestDatabaseId } from './types'; diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index ff1f2ee460..72abd908c5 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -20,6 +20,7 @@ * @packageDocumentation */ +export * from './cache'; export * from './database'; export * from './msw'; export * from './filesystem'; diff --git a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts index b411086728..05eeabd91a 100644 --- a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts +++ b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts @@ -20,7 +20,7 @@ export function isDockerDisabledForTests() { // the (relatively heavy, long running) docker based tests. If you want to // still run local tests for all databases, just pass either the CI=1 env // parameter to your test runner, or individual connection strings per - // database. + // database or cache. return ( Boolean(process.env.BACKSTAGE_TEST_DISABLE_DOCKER) || !Boolean(process.env.CI) diff --git a/yarn.lock b/yarn.lock index 157f44465e..5a9f99ac22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3582,11 +3582,15 @@ __metadata: "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/types": "workspace:^" + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 + "@types/keyv": ^4.2.0 "@types/supertest": ^2.0.8 better-sqlite3: ^9.0.0 cookie: ^0.6.0 express: ^4.17.1 fs-extra: ^11.0.0 + keyv: ^4.5.2 knex: ^3.0.0 msw: ^1.0.0 mysql2: ^3.0.0 @@ -17510,7 +17514,16 @@ __metadata: languageName: node linkType: hard -"@types/keyv@npm:*, @types/keyv@npm:^3.1.1": +"@types/keyv@npm:*, @types/keyv@npm:^4.2.0": + version: 4.2.0 + resolution: "@types/keyv@npm:4.2.0" + dependencies: + keyv: "*" + checksum: 8713da9382b9346d664866a6cab2f91b0fd479f61379af891303a618e9a2abad6f347adc38a0850540e3f2dad278427de24e7555339264fddb04d1d17d3b50e0 + languageName: node + linkType: hard + +"@types/keyv@npm:^3.1.1": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" dependencies: @@ -31023,6 +31036,15 @@ __metadata: languageName: node linkType: hard +"keyv@npm:*, keyv@npm:^4.0.0, keyv@npm:^4.5.2": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: 3.0.1 + checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72 + languageName: node + linkType: hard + "keyv@npm:^3.0.0": version: 3.1.0 resolution: "keyv@npm:3.1.0" @@ -31032,15 +31054,6 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.0.0, keyv@npm:^4.5.2": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: 3.0.1 - checksum: 74a24395b1c34bd44ad5cb2b49140d087553e170625240b86755a6604cd65aa16efdbdeae5cdb17ba1284a0fbb25ad06263755dbc71b8d8b06f74232ce3cdd72 - languageName: node - linkType: hard - "kind-of@npm:^6.0.2, kind-of@npm:^6.0.3": version: 6.0.3 resolution: "kind-of@npm:6.0.3" From 539b1382afbb13b296d941d411b8b506d03abad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 23 May 2024 09:49:53 +0200 Subject: [PATCH 521/567] improve the config test too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/proxy-backend/package.json | 4 +- .../src/service/router.config.test.ts | 111 ++++++++---------- .../src/service/router.credentials.test.ts | 4 +- yarn.lock | 2 - 4 files changed, 53 insertions(+), 68 deletions(-) diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 1ca6cbe5f5..3d3ccfaa0d 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -71,13 +71,11 @@ "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", "@types/http-proxy-middleware": "^1.0.0", - "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", "msw": "^2.0.0", "node-fetch": "^2.6.7", - "portfinder": "^1.0.32", - "supertest": "^6.1.3" + "portfinder": "^1.0.32" }, "configSchema": "config.d.ts" } diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index 20aed5452a..c868092612 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -14,51 +14,46 @@ * limitations under the License. */ +import { createBackend } from '@backstage/backend-defaults'; import { - HostDiscovery, - loggerToWinstonLogger, -} from '@backstage/backend-common'; + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { ConfigSources, MutableConfigSource, StaticConfigSource, } from '@backstage/config-loader'; -import express from 'express'; -import { http, HttpResponse } from 'msw'; +import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; -import request from 'supertest'; -import { createRouter } from './router'; -import { mockServices } from '@backstage/backend-test-utils'; +import fetch from 'node-fetch'; +import portFinder from 'portfinder'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file describe('createRouter reloadable configuration', () => { - const server = setupServer( - http.get('https://non-existing-example.com/', req => - HttpResponse.json({ - url: req.request.url.toString(), - headers: req.request.headers, - }), - ), - ); - - beforeAll(() => - server.listen({ - onUnhandledRequest: ({ headers }, print) => { - if (headers.get('User-Agent') === 'supertest') { - return; - } - print.error(); - }, - }), - ); - - afterAll(() => server.close()); - afterEach(() => server.resetHandlers()); + const server = setupServer(); + setupRequestMockHandlers(server); it('should be able to observe the config', async () => { - const logger = loggerToWinstonLogger(mockServices.logger.mock()); + const host = 'localhost'; + const port = await portFinder.getPortPromise({ host }); + const baseUrl = `http://${host}:${port}`; + + server.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('https://non-existing-example.com/*', req => + HttpResponse.json({ + url: req.request.url.toString(), + headers: req.request.headers, + }), + ), + ); // Grab the subscriber function and use mutable config data to mock a config file change const mutableConfigSource = MutableConfigSource.create({ data: {} }); @@ -67,18 +62,14 @@ describe('createRouter reloadable configuration', () => { StaticConfigSource.create({ data: { backend: { - baseUrl: 'http://localhost:7007', - listen: { - port: 7007, - }, + baseUrl, + listen: { host, port }, }, proxy: { endpoints: { '/test': { target: 'https://non-existing-example.com', - pathRewrite: { - '.*': '/', - }, + credentials: 'dangerously-allow-unauthenticated', }, }, }, @@ -88,40 +79,38 @@ describe('createRouter reloadable configuration', () => { ]), ); - const discovery = HostDiscovery.fromConfig(config); - const router = await createRouter({ - config, - logger, - discovery, + const backend = createBackend(); + backend.add(import('../alpha')); + backend.add( + createServiceFactory({ + service: coreServices.rootConfig, + deps: {}, + factory: () => config, + }), + ); + backend.add(mockServices.rootLogger.factory()); + await backend.start(); + + await expect(fetch(`${baseUrl}/api/proxy/test`)).resolves.toMatchObject({ + status: 200, }); - expect(router).toBeDefined(); - - const app = express(); - app.use(router); - - const agent = request.agent(app); - // this is set to let msw pass test requests through the mock server - agent.set('User-Agent', 'supertest'); - - const response1 = await agent.get('/test'); - - expect(response1.status).toEqual(200); + await expect( + fetch(`${baseUrl}/api/proxy/test2`), + ).resolves.not.toMatchObject({ status: 200 }); mutableConfigSource.setData({ proxy: { endpoints: { '/test2': { target: 'https://non-existing-example.com', - pathRewrite: { - '.*': '/', - }, + credentials: 'dangerously-allow-unauthenticated', }, }, }, }); - const response2 = await agent.get('/test2'); - - expect(response2.status).toEqual(200); + await expect(fetch(`${baseUrl}/api/proxy/test2`)).resolves.toMatchObject({ + status: 200, + }); }); }); diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 85fad9f038..53d0f157f3 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -35,7 +35,7 @@ describe('credentials', () => { it('handles all valid credentials settings', async () => { const host = 'localhost'; - const port = await portFinder.getPortPromise(); + const port = await portFinder.getPortPromise({ host }); const baseUrl = `http://${host}:${port}`; const config = { @@ -82,7 +82,7 @@ describe('credentials', () => { }; worker.use( - http.all(`${baseUrl}/*`, () => passthrough()), + http.all(`${baseUrl}/*`, passthrough), http.get('http://target.com/*', req => { const auth = req.request.headers.get('authorization'); return HttpResponse.json({ diff --git a/yarn.lock b/yarn.lock index a823457dce..3288ed9a2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6464,7 +6464,6 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/http-proxy-middleware": ^1.0.0 - "@types/supertest": ^2.0.8 "@types/uuid": ^9.0.0 "@types/yup": ^0.32.0 express: ^4.17.1 @@ -6474,7 +6473,6 @@ __metadata: msw: ^2.0.0 node-fetch: ^2.6.7 portfinder: ^1.0.32 - supertest: ^6.1.3 uuid: ^9.0.0 winston: ^3.2.1 yaml: ^2.0.0 From 206ce36aa244e511627b7ef9938f3af82b16135c Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 10:39:51 +0200 Subject: [PATCH 522/567] Use fetch Signed-off-by: Alex Eftimie --- .../src/api/CatalogImportClient.test.ts | 19 ++---- .../src/api/CatalogImportClient.ts | 54 ++++++++--------- .../src/api/KubernetesBackendClient.test.ts | 6 +- .../src/api/KubernetesBackendClient.ts | 28 +++------ plugins/search/src/alpha.tsx | 7 ++- plugins/search/src/apis.test.ts | 60 ++++++++++--------- plugins/search/src/apis.ts | 16 ++--- plugins/search/src/plugin.ts | 8 +-- 8 files changed, 86 insertions(+), 112 deletions(-) diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index df44ec94c4..98cf972446 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -50,7 +50,7 @@ import { ConfigReader, UrlPatternDiscovery } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { ScmAuthApi } from '@backstage/integration-react'; import { CatalogApi } from '@backstage/plugin-catalog-react'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; import { Octokit } from '@octokit/rest'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -66,14 +66,7 @@ describe('CatalogImportClient', () => { const scmAuthApi: jest.Mocked = { getCredentials: jest.fn().mockResolvedValue({ token: 'token' }), }; - const identityApi = { - signOut: () => { - return Promise.resolve(); - }, - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn().mockResolvedValue({ token: 'token' }), - }; + const fetchApi = new MockFetchApi(); const scmIntegrationsApi = ScmIntegrations.fromConfig( new ConfigReader({ @@ -110,7 +103,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ app: { @@ -456,7 +449,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ catalog: { @@ -659,7 +652,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ catalog: { @@ -745,7 +738,7 @@ describe('CatalogImportClient', () => { discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi: catalogApi as Partial as CatalogApi, configApi: new ConfigReader({ catalog: { diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index cecb85f9a3..ec5950ec51 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,11 +15,7 @@ */ import { CatalogApi } from '@backstage/catalog-client'; -import { - ConfigApi, - DiscoveryApi, - IdentityApi, -} from '@backstage/core-plugin-api'; +import { ConfigApi, DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; import { GithubIntegrationConfig, ScmIntegrationRegistry, @@ -41,7 +37,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; */ export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; + private readonly fetchApi: FetchApi; private readonly scmAuthApi: ScmAuthApi; private readonly scmIntegrationsApi: ScmIntegrationRegistry; private readonly catalogApi: CatalogApi; @@ -50,14 +46,14 @@ export class CatalogImportClient implements CatalogImportApi { constructor(options: { discoveryApi: DiscoveryApi; scmAuthApi: ScmAuthApi; - identityApi: IdentityApi; + fetchApi: FetchApi; scmIntegrationsApi: ScmIntegrationRegistry; catalogApi: CatalogApi; configApi: ConfigApi; }) { this.discoveryApi = options.discoveryApi; this.scmAuthApi = options.scmAuthApi; - this.identityApi = options.identityApi; + this.fetchApi = options.fetchApi; this.scmIntegrationsApi = options.scmIntegrationsApi; this.catalogApi = options.catalogApi; this.configApi = options.configApi; @@ -206,29 +202,29 @@ the component will become available.\n\nFor more information, read an \ private async analyzeLocation(options: { repo: string; }): Promise { - const { token } = await this.identityApi.getCredentials(); - const response = await fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, - { - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - method: 'POST', - body: JSON.stringify({ - location: { type: 'url', target: options.repo }, - ...(this.configApi.getOptionalString( - 'catalog.import.entityFilename', - ) && { - catalogFilename: this.configApi.getOptionalString( + const response = await this.fetchApi + .fetch( + `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, + { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + location: { type: 'url', target: options.repo }, + ...(this.configApi.getOptionalString( 'catalog.import.entityFilename', - ), + ) && { + catalogFilename: this.configApi.getOptionalString( + 'catalog.import.entityFilename', + ), + }), }), - }), - }, - ).catch(e => { - throw new Error(`Failed to generate entity definitions, ${e.message}`); - }); + }, + ) + .catch(e => { + throw new Error(`Failed to generate entity definitions, ${e.message}`); + }); if (!response.ok) { throw new Error( `Failed to generate entity definitions. Received http response ${response.status}: ${response.statusText}`, diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts index 0535e404f6..82ac7c84e2 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts @@ -19,7 +19,7 @@ import { KubernetesBackendClient } from './KubernetesBackendClient'; import { rest } from 'msw'; import { UrlPatternDiscovery } from '@backstage/core-app-api'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; import { CustomObjectsByEntityRequest, KubernetesRequestBody, @@ -44,6 +44,7 @@ describe('KubernetesBackendClient', () => { getBackstageIdentity: jest.fn(), signOut: jest.fn(), }; + const fetchApi = new MockFetchApi({ injectIdentityAuth: { identityApi } }); beforeEach(() => { jest.resetAllMocks(); @@ -51,7 +52,7 @@ describe('KubernetesBackendClient', () => { discoveryApi: UrlPatternDiscovery.compile( 'http://localhost:1234/api/{{ pluginId }}', ), - identityApi, + fetchApi, kubernetesAuthProvidersApi, }); mockResponse = { @@ -454,6 +455,7 @@ describe('KubernetesBackendClient', () => { }); it('hits the /proxy API with serviceAccount as auth provider', async () => { + identityApi.getCredentials.mockResolvedValue({ token: 'idToken' }); worker.use( rest.get( 'http://localhost:1234/api/kubernetes/clusters', diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts index 461b1d1ffe..f0bd0fb945 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts @@ -21,7 +21,7 @@ import { WorkloadsByEntityRequest, CustomObjectsByEntityRequest, } from '@backstage/plugin-kubernetes-common'; -import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { KubernetesAuthProvidersApi } from '../kubernetes-auth-provider'; import { NotFoundError } from '@backstage/errors'; @@ -29,16 +29,16 @@ import { NotFoundError } from '@backstage/errors'; /** @public */ export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; + private readonly fetchApi: FetchApi; private readonly kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; constructor(options: { discoveryApi: DiscoveryApi; - identityApi: IdentityApi; + fetchApi: FetchApi; kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; }) { this.discoveryApi = options.discoveryApi; - this.identityApi = options.identityApi; + this.fetchApi = options.fetchApi; this.kubernetesAuthProvidersApi = options.kubernetesAuthProvidersApi; } @@ -62,12 +62,10 @@ export class KubernetesBackendClient implements KubernetesApi { private async postRequired(path: string, requestBody: any): Promise { const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}${path}`; - const { token: idToken } = await this.identityApi.getCredentials(); - const response = await fetch(url, { + const response = await this.fetchApi.fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', - ...(idToken && { Authorization: `Bearer ${idToken}` }), }, body: JSON.stringify(requestBody), }); @@ -130,14 +128,8 @@ export class KubernetesBackendClient implements KubernetesApi { } async getClusters(): Promise<{ name: string; authProvider: string }[]> { - const { token: idToken } = await this.identityApi.getCredentials(); const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/clusters`; - const response = await fetch(url, { - method: 'GET', - headers: { - ...(idToken && { Authorization: `Bearer ${idToken}` }), - }, - }); + const response = await this.fetchApi.fetch(url); return (await this.handleResponse(response)).items; } @@ -157,15 +149,13 @@ export class KubernetesBackendClient implements KubernetesApi { const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/proxy${ options.path }`; - const identityResponse = await this.identityApi.getCredentials(); const headers = KubernetesBackendClient.getKubernetesHeaders( options, kubernetesCredentials?.token, - identityResponse, authProvider, oidcTokenProvider, ); - return await fetch(url, { ...options.init, headers }); + return await this.fetchApi.fetch(url, { ...options.init, headers }); } private static getKubernetesHeaders( @@ -175,7 +165,6 @@ export class KubernetesBackendClient implements KubernetesApi { init?: RequestInit; }, k8sToken: string | undefined, - identityResponse: { token?: string }, authProvider: string, oidcTokenProvider: string | undefined, ) { @@ -190,9 +179,6 @@ export class KubernetesBackendClient implements KubernetesApi { ...(k8sToken && { [kubernetesAuthHeader]: k8sToken, }), - ...(identityResponse.token && { - Authorization: `Bearer ${identityResponse.token}`, - }), }; } diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index 9fffa41bfb..ca22a57c54 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -35,6 +35,7 @@ import { IdentityApi, discoveryApiRef, identityApiRef, + FetchApi, } from '@backstage/core-plugin-api'; import { @@ -81,12 +82,12 @@ export const searchApi = createApiExtension({ api: searchApiRef, deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, factory: ({ - identityApi, + fetchApi, discoveryApi, }: { - identityApi: IdentityApi; + fetchApi: FetchApi; discoveryApi: DiscoveryApi; - }) => new SearchClient({ discoveryApi, identityApi }), + }) => new SearchClient({ discoveryApi, fetchApi }), }, }); diff --git a/plugins/search/src/apis.test.ts b/plugins/search/src/apis.test.ts index 7996ad3fa3..5d0112c7c7 100644 --- a/plugins/search/src/apis.test.ts +++ b/plugins/search/src/apis.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { MockFetchApi } from '@backstage/test-utils'; import { SearchClient } from './apis'; describe('apis', () => { @@ -26,48 +27,49 @@ describe('apis', () => { const baseUrl = 'https://base-url.com/'; const getBaseUrl = jest.fn().mockResolvedValue(baseUrl); - const token = 'AUTHTOKEN'; - const withToken = jest.fn().mockResolvedValue({ token }); - const withoutToken = jest.fn().mockResolvedValue({ token: undefined }); - const createIdentityApiMock = (getCredentials: any) => ({ - signOut: jest.fn(), + const identityApi = { + getCredentials: jest.fn(), getProfileInfo: jest.fn(), getBackstageIdentity: jest.fn(), - getCredentials, + signOut: jest.fn(), + }; + const json = jest.fn(); + const mockFetch = jest.fn().mockResolvedValue({ + ok: true, + json, + }); + const fetchApi = new MockFetchApi({ + baseImplementation: mockFetch, + injectIdentityAuth: { identityApi }, }); const client = new SearchClient({ discoveryApi: { getBaseUrl }, - identityApi: createIdentityApiMock(withoutToken), - }); - - const json = jest.fn(); - const originalFetch = window.fetch; - window.fetch = jest.fn().mockResolvedValue({ json, ok: true }); - - afterAll(() => { - window.fetch = originalFetch; + fetchApi, }); it('Fetch is called with expected URL (including stringified Q params)', async () => { + identityApi.getCredentials.mockResolvedValue({}); await client.query(query); expect(getBaseUrl).toHaveBeenLastCalledWith('search'); - expect(fetch).toHaveBeenLastCalledWith(`${baseUrl}/query?term=`, { - headers: {}, - }); + expect(mockFetch).toHaveBeenLastCalledWith( + `${baseUrl}/query?term=`, + undefined, + ); }); - it('Sets Authorization if token is available', async () => { - const authedClient = new SearchClient({ - discoveryApi: { getBaseUrl }, - identityApi: createIdentityApiMock(withToken), - }); - await authedClient.query(query); - expect(getBaseUrl).toHaveBeenLastCalledWith('search'); - expect(fetch).toHaveBeenLastCalledWith(`${baseUrl}/query?term=`, { - headers: { Authorization: `Bearer ${token}` }, - }); - }); + // it('Sets Authorization if token is available', async () => { + // identityApi.getCredentials.mockResolvedValue({ token: 'token' }); + // await client.query(query); + // expect(getBaseUrl).toHaveBeenLastCalledWith('search'); + // expect(mockFetch).toHaveBeenLastCalledWith( + // expect.objectContaining({ + // agent: undefined, + // query: 'term=', + // headers: { authorization: ["Bearer token"] } + // }) + // ); + // }); it('Resolves JSON from fetch response', async () => { const result = { loading: false, error: '', value: {} }; diff --git a/plugins/search/src/apis.ts b/plugins/search/src/apis.ts index eb5f47a65c..b56d88a22c 100644 --- a/plugins/search/src/apis.ts +++ b/plugins/search/src/apis.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; +import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { SearchApi } from '@backstage/plugin-search-react'; import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common'; @@ -23,25 +23,19 @@ import qs from 'qs'; export class SearchClient implements SearchApi { private readonly discoveryApi: DiscoveryApi; - private readonly identityApi: IdentityApi; + private readonly fetchApi: FetchApi; - constructor(options: { - discoveryApi: DiscoveryApi; - identityApi: IdentityApi; - }) { + constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi }) { this.discoveryApi = options.discoveryApi; - this.identityApi = options.identityApi; + this.fetchApi = options.fetchApi; } async query(query: SearchQuery): Promise { - const { token } = await this.identityApi.getCredentials(); const queryString = qs.stringify(query); const url = `${await this.discoveryApi.getBaseUrl( 'search', )}/query?${queryString}`; - const response = await fetch(url, { - headers: token ? { Authorization: `Bearer ${token}` } : {}, - }); + const response = await this.fetchApi.fetch(url); if (!response.ok) { throw await ResponseError.fromResponse(response); diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 07a645f73e..1adfd2e684 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -23,7 +23,7 @@ import { createRoutableExtension, discoveryApiRef, createComponentExtension, - identityApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ @@ -38,9 +38,9 @@ export const searchPlugin = createPlugin({ apis: [ createApiFactory({ api: searchApiRef, - deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, - factory: ({ discoveryApi, identityApi }) => { - return new SearchClient({ discoveryApi, identityApi }); + deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef }, + factory: ({ discoveryApi, fetchApi }) => { + return new SearchClient({ discoveryApi, fetchApi }); }, }), ], From bbd19b7c45a218448850f5d0851684cd7f5d056a Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 11:25:22 +0200 Subject: [PATCH 523/567] fix build Signed-off-by: Alex Eftimie --- plugins/catalog-import/api-report.md | 4 ++-- plugins/catalog-import/src/alpha.tsx | 8 +++---- .../DefaultImportPage.test.tsx | 20 +++-------------- .../components/ImportPage/ImportPage.test.tsx | 22 ++++--------------- plugins/catalog-import/src/plugin.ts | 8 +++---- plugins/kubernetes-react/api-report.md | 4 ++-- plugins/kubernetes/src/plugin.ts | 8 +++---- plugins/search/src/alpha.tsx | 1 - 8 files changed, 23 insertions(+), 52 deletions(-) diff --git a/plugins/catalog-import/api-report.md b/plugins/catalog-import/api-report.md index a513cfe87a..c61aa97b06 100644 --- a/plugins/catalog-import/api-report.md +++ b/plugins/catalog-import/api-report.md @@ -13,8 +13,8 @@ import { ConfigApi } from '@backstage/core-plugin-api'; import { Controller } from 'react-hook-form'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { FetchApi } from '@backstage/core-plugin-api'; import { FieldErrors } from 'react-hook-form'; -import { IdentityApi } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { default as React_2 } from 'react'; @@ -102,7 +102,7 @@ export class CatalogImportClient implements CatalogImportApi { constructor(options: { discoveryApi: DiscoveryApi; scmAuthApi: ScmAuthApi; - identityApi: IdentityApi; + fetchApi: FetchApi; scmIntegrationsApi: ScmIntegrationRegistry; catalogApi: CatalogApi; configApi: ConfigApi; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 304ee3f272..795e697874 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -18,7 +18,7 @@ import { configApiRef, createApiFactory, discoveryApiRef, - identityApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { compatWrapper, @@ -55,7 +55,7 @@ const catalogImportApi = createApiExtension({ deps: { discoveryApi: discoveryApiRef, scmAuthApi: scmAuthApiRef, - identityApi: identityApiRef, + fetchApi: fetchApiRef, scmIntegrationsApi: scmIntegrationsApiRef, catalogApi: catalogApiRef, configApi: configApiRef, @@ -63,7 +63,7 @@ const catalogImportApi = createApiExtension({ factory: ({ discoveryApi, scmAuthApi, - identityApi, + fetchApi, scmIntegrationsApi, catalogApi, configApi, @@ -72,7 +72,7 @@ const catalogImportApi = createApiExtension({ discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi, configApi, }), diff --git a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx index a231563484..929594f22a 100644 --- a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx +++ b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx @@ -25,22 +25,8 @@ import { catalogImportApiRef, CatalogImportClient } from '../../api'; import { DefaultImportPage } from './DefaultImportPage'; describe('', () => { - const identityApi = { - getUserId: () => { - return 'user'; - }, - getProfile: () => { - return {}; - }, - getIdToken: () => { - return Promise.resolve('token'); - }, - signOut: () => { - return Promise.resolve(); - }, - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + const fetchApi = { + fetch: jest.fn(), }; let apis: TestApiRegistry; @@ -56,7 +42,7 @@ describe('', () => { scmAuthApi: { getCredentials: async () => ({ token: 'token', headers: {} }), }, - identityApi, + fetchApi, scmIntegrationsApi: {} as any, catalogApi: {} as any, configApi: {} as any, diff --git a/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx b/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx index 1b0ddcfc04..e1c4edb896 100644 --- a/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx +++ b/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx @@ -16,7 +16,7 @@ import { CatalogClient } from '@backstage/catalog-client'; import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; -import { configApiRef } from '@backstage/core-plugin-api'; +import { FetchApi, configApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; @@ -31,22 +31,8 @@ jest.mock('react-router-dom', () => ({ })); describe('', () => { - const identityApi = { - getUserId: () => { - return 'user'; - }, - getProfile: () => { - return {}; - }, - getIdToken: () => { - return Promise.resolve('token'); - }, - signOut: () => { - return Promise.resolve(); - }, - getProfileInfo: jest.fn(), - getBackstageIdentity: jest.fn(), - getCredentials: jest.fn(), + const fetchApi: FetchApi = { + fetch: jest.fn(), }; let apis: TestApiRegistry; @@ -59,7 +45,7 @@ describe('', () => { catalogImportApiRef, new CatalogImportClient({ discoveryApi: {} as any, - identityApi, + fetchApi, scmAuthApi: {} as any, scmIntegrationsApi: {} as any, catalogApi: {} as any, diff --git a/plugins/catalog-import/src/plugin.ts b/plugins/catalog-import/src/plugin.ts index d9dd226792..62741c27c0 100644 --- a/plugins/catalog-import/src/plugin.ts +++ b/plugins/catalog-import/src/plugin.ts @@ -21,7 +21,7 @@ import { createRoutableExtension, createRouteRef, discoveryApiRef, - identityApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { scmAuthApiRef, @@ -48,7 +48,7 @@ export const catalogImportPlugin = createPlugin({ deps: { discoveryApi: discoveryApiRef, scmAuthApi: scmAuthApiRef, - identityApi: identityApiRef, + fetchApi: fetchApiRef, scmIntegrationsApi: scmIntegrationsApiRef, catalogApi: catalogApiRef, configApi: configApiRef, @@ -56,7 +56,7 @@ export const catalogImportPlugin = createPlugin({ factory: ({ discoveryApi, scmAuthApi, - identityApi, + fetchApi, scmIntegrationsApi, catalogApi, configApi, @@ -65,7 +65,7 @@ export const catalogImportPlugin = createPlugin({ discoveryApi, scmAuthApi, scmIntegrationsApi, - identityApi, + fetchApi, catalogApi, configApi, }), diff --git a/plugins/kubernetes-react/api-report.md b/plugins/kubernetes-react/api-report.md index ccc2e1643d..badf80a87f 100644 --- a/plugins/kubernetes-react/api-report.md +++ b/plugins/kubernetes-react/api-report.md @@ -16,10 +16,10 @@ import { DetectedErrorsByCluster } from '@backstage/plugin-kubernetes-common'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { Event as Event_2 } from 'kubernetes-models/v1'; +import { FetchApi } from '@backstage/core-plugin-api'; import { GroupedResponses } from '@backstage/plugin-kubernetes-common'; import { IContainer } from 'kubernetes-models/v1'; import { IContainerStatus } from 'kubernetes-models/v1'; -import { IdentityApi } from '@backstage/core-plugin-api'; import { IIoK8sApimachineryPkgApisMetaV1ObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta'; import { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta'; import { JsonObject } from '@backstage/types'; @@ -402,7 +402,7 @@ export const kubernetesAuthProvidersApiRef: ApiRef; export class KubernetesBackendClient implements KubernetesApi { constructor(options: { discoveryApi: DiscoveryApi; - identityApi: IdentityApi; + fetchApi: FetchApi; kubernetesAuthProvidersApi: KubernetesAuthProvidersApi; }); // (undocumented) diff --git a/plugins/kubernetes/src/plugin.ts b/plugins/kubernetes/src/plugin.ts index afe2287633..eaa714dbea 100644 --- a/plugins/kubernetes/src/plugin.ts +++ b/plugins/kubernetes/src/plugin.ts @@ -30,13 +30,13 @@ import { createPlugin, createRouteRef, discoveryApiRef, - identityApiRef, gitlabAuthApiRef, googleAuthApiRef, microsoftAuthApiRef, oktaAuthApiRef, oneloginAuthApiRef, createRoutableExtension, + fetchApiRef, } from '@backstage/core-plugin-api'; export const rootCatalogKubernetesRouteRef = createRouteRef({ @@ -50,13 +50,13 @@ export const kubernetesPlugin = createPlugin({ api: kubernetesApiRef, deps: { discoveryApi: discoveryApiRef, - identityApi: identityApiRef, + fetchApi: fetchApiRef, kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, }, - factory: ({ discoveryApi, identityApi, kubernetesAuthProvidersApi }) => + factory: ({ discoveryApi, fetchApi, kubernetesAuthProvidersApi }) => new KubernetesBackendClient({ discoveryApi, - identityApi, + fetchApi, kubernetesAuthProvidersApi, }), }), diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index ca22a57c54..5c85d097d4 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -32,7 +32,6 @@ import { import { useApi, DiscoveryApi, - IdentityApi, discoveryApiRef, identityApiRef, FetchApi, From 4f92394b1a0511cc77fefc9946e415da925f89f7 Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 11:26:22 +0200 Subject: [PATCH 524/567] Add changeset Signed-off-by: Alex Eftimie --- .changeset/empty-tables-ring.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/empty-tables-ring.md diff --git a/.changeset/empty-tables-ring.md b/.changeset/empty-tables-ring.md new file mode 100644 index 0000000000..1b52801ae0 --- /dev/null +++ b/.changeset/empty-tables-ring.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-kubernetes-react': minor +'@backstage/plugin-catalog-import': minor +'@backstage/plugin-kubernetes': minor +'@backstage/plugin-search': minor +--- + +Migrate from identityApi to fetchApi in frontend plugins. From 6d196b4506e002552b6d9258d2c639c33f86e7b9 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Thu, 23 May 2024 11:26:40 +0200 Subject: [PATCH 525/567] fix: Avoid infinite loop in the NotificationsSidebarItem title counter Signed-off-by: Marek Libra --- .changeset/little-cooks-approve.md | 5 +++ .../src/hooks/useTitleCounter.ts | 31 ++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 .changeset/little-cooks-approve.md diff --git a/.changeset/little-cooks-approve.md b/.changeset/little-cooks-approve.md new file mode 100644 index 0000000000..d4088208ae --- /dev/null +++ b/.changeset/little-cooks-approve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Fixes performance issue with Notifications title counter. diff --git a/plugins/notifications/src/hooks/useTitleCounter.ts b/plugins/notifications/src/hooks/useTitleCounter.ts index d794678ea6..0793cd58d6 100644 --- a/plugins/notifications/src/hooks/useTitleCounter.ts +++ b/plugins/notifications/src/hooks/useTitleCounter.ts @@ -13,38 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import throttle from 'lodash/throttle'; + +const getPrefix = (value: number) => (value === 0 ? '' : `(${value}) `); + +const cleanTitle = (currentTitle: string) => + currentTitle.replace(/^\(\d+\)\s/, ''); + +const throttledSetTitle = throttle((shownTitle: string) => { + document.title = shownTitle; +}, 100); /** @public */ export function useTitleCounter() { const [title, setTitle] = useState(document.title); const [count, setCount] = useState(0); - const titleTimer = useRef(undefined); - - const getPrefix = (value: number) => { - return value === 0 ? '' : `(${value}) `; - }; - - const cleanTitle = (currentTitle: string) => { - return currentTitle.replace(/^\(\d+\)\s/, ''); - }; useEffect(() => { const baseTitle = cleanTitle(title); const shownTitle = `${getPrefix(count)}${baseTitle}`; if (document.title !== shownTitle) { - window.clearTimeout(titleTimer.current); - document.title = shownTitle; - // Need to do this in timeout as the React Helmet overrides the title after this effect - titleTimer.current = window.setTimeout(() => { - document.title = shownTitle; - }, 50); + throttledSetTitle(shownTitle); } return () => { - window.clearTimeout(titleTimer.current); document.title = cleanTitle(title); }; - }, [title, count]); + }, [count, title]); useEffect(() => { const titleElement = document.querySelector('title'); From fdcaf5d93829f5a2f23fe756a9d1b22aa3563262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 23 May 2024 11:37:47 +0200 Subject: [PATCH 526/567] move to startTestBackend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/proxy-backend/package.json | 4 +- .../src/service/router.config.test.ts | 93 +++++++++---------- .../src/service/router.credentials.test.ts | 83 ++++++++--------- yarn.lock | 2 +- 4 files changed, 87 insertions(+), 95 deletions(-) diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 3d3ccfaa0d..af0d9d584e 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -65,6 +65,7 @@ "yup": "^1.0.0" }, "devDependencies": { + "@backstage/backend-app-api": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", @@ -74,8 +75,7 @@ "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", "msw": "^2.0.0", - "node-fetch": "^2.6.7", - "portfinder": "^1.0.32" + "node-fetch": "^2.6.7" }, "configSchema": "config.d.ts" } diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index c868092612..ba81553a4f 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -14,14 +14,13 @@ * limitations under the License. */ -import { createBackend } from '@backstage/backend-defaults'; import { coreServices, createServiceFactory, } from '@backstage/backend-plugin-api'; import { - mockServices, setupRequestMockHandlers, + startTestBackend, } from '@backstage/backend-test-utils'; import { ConfigSources, @@ -31,7 +30,6 @@ import { import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'node-fetch'; -import portFinder from 'portfinder'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file @@ -41,30 +39,12 @@ describe('createRouter reloadable configuration', () => { setupRequestMockHandlers(server); it('should be able to observe the config', async () => { - const host = 'localhost'; - const port = await portFinder.getPortPromise({ host }); - const baseUrl = `http://${host}:${port}`; - - server.use( - http.all(`${baseUrl}/*`, passthrough), - http.get('https://non-existing-example.com/*', req => - HttpResponse.json({ - url: req.request.url.toString(), - headers: req.request.headers, - }), - ), - ); - // Grab the subscriber function and use mutable config data to mock a config file change const mutableConfigSource = MutableConfigSource.create({ data: {} }); const config = await ConfigSources.toConfig( ConfigSources.merge([ StaticConfigSource.create({ data: { - backend: { - baseUrl, - listen: { host, port }, - }, proxy: { endpoints: { '/test': { @@ -79,38 +59,53 @@ describe('createRouter reloadable configuration', () => { ]), ); - const backend = createBackend(); - backend.add(import('../alpha')); - backend.add( - createServiceFactory({ - service: coreServices.rootConfig, - deps: {}, - factory: () => config, - }), - ); - backend.add(mockServices.rootLogger.factory()); - await backend.start(); - - await expect(fetch(`${baseUrl}/api/proxy/test`)).resolves.toMatchObject({ - status: 200, + const backend = await startTestBackend({ + features: [ + import('../alpha'), + createServiceFactory({ + service: coreServices.rootConfig, + deps: {}, + factory: () => config, + }), + ], }); - await expect( - fetch(`${baseUrl}/api/proxy/test2`), - ).resolves.not.toMatchObject({ status: 200 }); - mutableConfigSource.setData({ - proxy: { - endpoints: { - '/test2': { - target: 'https://non-existing-example.com', - credentials: 'dangerously-allow-unauthenticated', + try { + const baseUrl = `http://localhost:${backend.server.port()}`; + + server.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('https://non-existing-example.com/*', req => + HttpResponse.json({ + url: req.request.url.toString(), + headers: req.request.headers, + }), + ), + ); + + await expect(fetch(`${baseUrl}/api/proxy/test`)).resolves.toMatchObject({ + status: 200, + }); + await expect( + fetch(`${baseUrl}/api/proxy/test2`), + ).resolves.not.toMatchObject({ status: 200 }); + + mutableConfigSource.setData({ + proxy: { + endpoints: { + '/test2': { + target: 'https://non-existing-example.com', + credentials: 'dangerously-allow-unauthenticated', + }, }, }, - }, - }); + }); - await expect(fetch(`${baseUrl}/api/proxy/test2`)).resolves.toMatchObject({ - status: 200, - }); + await expect(fetch(`${baseUrl}/api/proxy/test2`)).resolves.toMatchObject({ + status: 200, + }); + } finally { + await backend.stop(); + } }); }); diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 53d0f157f3..54c865ba11 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -14,17 +14,20 @@ * limitations under the License. */ -import { createBackend } from '@backstage/backend-defaults'; +import { + authServiceFactory, + httpAuthServiceFactory, +} from '@backstage/backend-app-api'; import { mockServices, setupRequestMockHandlers, + startTestBackend, } from '@backstage/backend-test-utils'; import { ResponseError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; -import { http, HttpResponse, passthrough } from 'msw'; +import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'node-fetch'; -import portFinder from 'portfinder'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file @@ -34,14 +37,8 @@ describe('credentials', () => { setupRequestMockHandlers(worker); it('handles all valid credentials settings', async () => { - const host = 'localhost'; - const port = await portFinder.getPortPromise({ host }); - const baseUrl = `http://${host}:${port}`; - const config = { backend: { - baseUrl, - listen: { host, port }, auth: { externalAccess: [ { @@ -81,42 +78,42 @@ describe('credentials', () => { }, }; - worker.use( - http.all(`${baseUrl}/*`, passthrough), - http.get('http://target.com/*', req => { - const auth = req.request.headers.get('authorization'); - return HttpResponse.json({ - payload: { forwardedAuthorization: auth ?? false }, - }); - }), - ); - - async function call(options: { - endpoint: string; - authorization: string | false; - }): Promise { - const { endpoint, authorization } = options; - return fetch(`${baseUrl}/api/proxy/${endpoint}/just-some-path`, { - headers: authorization ? { Authorization: authorization } : {}, - }).then(async res => { - if (!res.ok) { - throw await ResponseError.fromResponse(res); - } - return res.json(); - }); - } - - // Create an actual backend instead of a test backend, because we want to - // use the real HTTP server that provides the protection middleware etc. A - // bit harder to test, but at least we can use static external access tokens - // for it. - const backend = createBackend(); - backend.add(import('../alpha')); - backend.add(mockServices.rootConfig.factory({ data: config })); - backend.add(mockServices.rootLogger.factory()); - await backend.start(); + const backend = await startTestBackend({ + features: [ + import('../alpha'), + mockServices.rootConfig.factory({ data: config }), + authServiceFactory(), + httpAuthServiceFactory(), + ], + }); try { + const baseUrl = `http://localhost:${backend.server.port()}`; + worker.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('http://target.com/*', req => { + const auth = req.request.headers.get('authorization'); + return HttpResponse.json({ + payload: { forwardedAuthorization: auth ?? false }, + }); + }), + ); + + const call = async (options: { + endpoint: string; + authorization: string | false; + }): Promise => { + const { endpoint, authorization } = options; + return fetch(`${baseUrl}/api/proxy/${endpoint}/just-some-path`, { + headers: authorization ? { Authorization: authorization } : {}, + }).then(async res => { + if (!res.ok) { + throw await ResponseError.fromResponse(res); + } + return res.json(); + }); + }; + // simple credentials config await expect( call({ endpoint: 'simple', authorization: false }), diff --git a/yarn.lock b/yarn.lock index 3288ed9a2e..329a17a855 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6453,6 +6453,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-proxy-backend@workspace:plugins/proxy-backend" dependencies: + "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" @@ -6472,7 +6473,6 @@ __metadata: morgan: ^1.10.0 msw: ^2.0.0 node-fetch: ^2.6.7 - portfinder: ^1.0.32 uuid: ^9.0.0 winston: ^3.2.1 yaml: ^2.0.0 From 8b53ded4c5ce4de8fc5c27120071ba8bebf93239 Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Thu, 23 May 2024 11:49:53 +0200 Subject: [PATCH 527/567] fix lint Signed-off-by: Alex Eftimie --- plugins/search/src/apis.test.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/plugins/search/src/apis.test.ts b/plugins/search/src/apis.test.ts index 5d0112c7c7..15745d2e68 100644 --- a/plugins/search/src/apis.test.ts +++ b/plugins/search/src/apis.test.ts @@ -58,19 +58,6 @@ describe('apis', () => { ); }); - // it('Sets Authorization if token is available', async () => { - // identityApi.getCredentials.mockResolvedValue({ token: 'token' }); - // await client.query(query); - // expect(getBaseUrl).toHaveBeenLastCalledWith('search'); - // expect(mockFetch).toHaveBeenLastCalledWith( - // expect.objectContaining({ - // agent: undefined, - // query: 'term=', - // headers: { authorization: ["Bearer token"] } - // }) - // ); - // }); - it('Resolves JSON from fetch response', async () => { const result = { loading: false, error: '', value: {} }; json.mockReturnValueOnce(result); From a9791bcec036045580aad607833d3a47e0bccc35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 23 May 2024 14:32:45 +0200 Subject: [PATCH 528/567] fix for new config shape too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../external/ExternalTokenHandler.test.ts | 156 ++++++++++++++++++ .../auth/external/jwks.test.ts | 98 +++++++---- .../implementations/auth/external/jwks.ts | 50 ++++-- 3 files changed, 258 insertions(+), 46 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts index 1e82f76134..559107c59c 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/ExternalTokenHandler.test.ts @@ -17,8 +17,72 @@ import { BackstagePrincipalAccessRestrictions } from '@backstage/backend-plugin-api'; import { ExternalTokenHandler } from './ExternalTokenHandler'; import { TokenHandler } from './types'; +import { + mockServices, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; +import { randomBytes } from 'crypto'; +import { SignJWT, exportJWK, generateKeyPair } from 'jose'; +import { DateTime } from 'luxon'; +import { v4 as uuid } from 'uuid'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; + +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend +interface AnyJWK extends Record { + use: 'sig'; + alg: string; + kid: string; + kty: string; +} +class FakeTokenFactory { + private readonly keys = new Array(); + + constructor( + private readonly options: { + issuer: string; + keyDurationSeconds: number; + }, + ) {} + + async issueToken(params: { + claims: { + sub: string; + ent?: string[]; + }; + }): Promise { + const pair = await generateKeyPair('RS256'); + const publicKey = await exportJWK(pair.publicKey); + const kid = uuid(); + publicKey.kid = kid; + this.keys.push(publicKey as AnyJWK); + + const iss = this.options.issuer; + const sub = params.claims.sub; + const ent = params.claims.ent; + const aud = 'backstage'; + const iat = Math.floor(Date.now() / 1000); + const exp = iat + this.options.keyDurationSeconds; + + return new SignJWT({ iss, sub, aud, iat, exp, ent, kid }) + .setProtectedHeader({ alg: 'RS256', ent: ent, kid: kid }) + .setIssuer(iss) + .setAudience(aud) + .setSubject(sub) + .setIssuedAt(iat) + .setExpirationTime(exp) + .sign(pair.privateKey); + } + + async listPublicKeys(): Promise<{ keys: AnyJWK[] }> { + return { keys: this.keys }; + } +} describe('ExternalTokenHandler', () => { + const server = setupServer(); + setupRequestMockHandlers(server); + it('skips over inner handlers that do not match, and applies plugin restrictions', async () => { const handler1: TokenHandler = { add: jest.fn(), @@ -52,4 +116,96 @@ describe('ExternalTokenHandler', () => { `"This token's access is restricted to plugin(s) 'plugin1'"`, ); }); + + it('successfully parses known methods', async () => { + const legacyKey = randomBytes(24); + + const factory = new FakeTokenFactory({ + issuer: 'blah', + keyDurationSeconds: 100, + }); + + server.use( + rest.get( + 'https://example.com/.well-known/jwks.json', + async (_, res, ctx) => { + const keys = await factory.listPublicKeys(); + return res(ctx.json(keys)); + }, + ), + ); + + const handler = ExternalTokenHandler.create({ + ownPluginId: 'catalog', + logger: mockServices.logger.mock(), + config: mockServices.rootConfig({ + data: { + backend: { + auth: { + externalAccess: [ + { + type: 'legacy', + options: { + secret: legacyKey.toString('base64'), + subject: 'legacy-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + { + type: 'static', + options: { + token: 'defdefdef', + subject: 'static-subject', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + { + type: 'jwks', + options: { + url: 'https://example.com/.well-known/jwks.json', + algorithm: 'RS256', + issuer: 'blah', + audience: 'backstage', + subjectPrefix: 'custom-prefix', + }, + accessRestrictions: [ + { plugin: 'catalog', permission: 'catalog.entity.read' }, + ], + }, + ], + }, + }, + }, + }), + }); + + const legacyToken = await new SignJWT({ + sub: 'backstage-server', + exp: DateTime.now().plus({ minutes: 1 }).toUnixInteger(), + }) + .setProtectedHeader({ alg: 'HS256' }) + .sign(legacyKey); + + await expect(handler.verifyToken(legacyToken)).resolves.toEqual({ + subject: 'legacy-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + + await expect(handler.verifyToken('defdefdef')).resolves.toEqual({ + subject: 'static-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + + const jwksToken = await factory.issueToken({ + claims: { sub: 'jwks-subject' }, + }); + await expect(handler.verifyToken(jwksToken)).resolves.toEqual({ + subject: 'external:custom-prefix:jwks-subject', + accessRestrictions: { permissionNames: ['catalog.entity.read'] }, + }); + }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts index 0466cdf034..56930e4480 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { SignJWT, exportJWK, generateKeyPair } from 'jose'; @@ -21,13 +22,13 @@ import { setupServer } from 'msw/node'; import { v4 as uuid } from 'uuid'; import { JWKSHandler } from './jwks'; +// Simplified copy of TokenFactory in @backstage/plugin-auth-backend interface AnyJWK extends Record { use: 'sig'; alg: string; kid: string; kty: string; } -// Simplified copy of TokenFactory in @backstage/plugin-auth-backend class FakeTokenFactory { private readonly keys = new Array(); @@ -100,10 +101,12 @@ describe('JWKSHandler', () => { it('verifies token with valid entry', async () => { const validEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: mockBaseUrl, - audience: 'backstage', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', + }, }; const jwksHandler = new JWKSHandler(); @@ -120,17 +123,21 @@ describe('JWKSHandler', () => { it('skips invalid entry and continues verification', async () => { const invalidEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: ['fakeIssuer'], - audience: ['fakeAud'], + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: ['fakeIssuer'], + audience: ['fakeAud'], + }, }; const validEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: ['multiple-issuers', mockBaseUrl], - audience: ['multiple-audiences', 'backstage'], + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: ['multiple-issuers', mockBaseUrl], + audience: ['multiple-audiences', 'backstage'], + }, }; const jwksHandler = new JWKSHandler(); @@ -148,15 +155,19 @@ describe('JWKSHandler', () => { it('returns undefined if no valid entry found', async () => { const invalidEntry1 = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: 'wrong', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: 'wrong', + }, }; const invalidEntry2 = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: ['HS256'], - audience: 'wrong', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: ['HS256'], + audience: 'wrong', + }, }; const jwksHandler = new JWKSHandler(); @@ -178,17 +189,21 @@ describe('JWKSHandler', () => { expect(() => { jwksHandler.add( new ConfigReader({ - url: 'https://exampl e.com/jwks', + options: { + url: 'https://exampl e.com/jwks', + }, }), ); - }).toThrow('Invalid URL'); + }).toThrow('Illegal JWKS URL, must be a set of non-space characters'); expect(() => { jwksHandler.add( new ConfigReader({ - url: 'https://example.com/jwks\n', + options: { + url: 'https://example.com/jwks\n', + }, }), ); - }).toThrow('Illegal URL, must be a set of non-space characters'); + }).toThrow('Illegal JWKS URL, must be a set of non-space characters'); }); it('gracefully handles no added tokens', async () => { @@ -198,11 +213,13 @@ describe('JWKSHandler', () => { it('uses custom subject prefix if provided', async () => { const validEntry = { - url: `${mockBaseUrl}/.well-known/jwks.json`, - algorithm: 'RS256', - issuer: mockBaseUrl, - audience: 'backstage', - subjectPrefix: 'custom-prefix', + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + algorithm: 'RS256', + issuer: mockBaseUrl, + audience: 'backstage', + subjectPrefix: 'custom-prefix', + }, }; const jwksHandler = new JWKSHandler(); @@ -215,7 +232,30 @@ describe('JWKSHandler', () => { const result = await jwksHandler.verifyToken(token); expect(result).toEqual({ - subject: `external:${validEntry.subjectPrefix}:${mockSubject}`, + subject: `external:${validEntry.options.subjectPrefix}:${mockSubject}`, + }); + }); + + it('carries over access restrictions', async () => { + const jwksHandler = new JWKSHandler(); + jwksHandler.add( + new ConfigReader({ + options: { + url: `${mockBaseUrl}/.well-known/jwks.json`, + }, + accessRestrictions: [{ plugin: 'scaffolder', permission: 'do.it' }], + }), + ); + + const token = await factory.issueToken({ claims: { sub: mockSubject } }); + + await expect(jwksHandler.verifyToken(token)).resolves.toEqual({ + subject: `external:${mockSubject}`, + allAccessRestrictions: new Map( + Object.entries({ + scaffolder: { permissionNames: ['do.it'] }, + }), + ), }); }); }); diff --git a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts index 8af44f4d54..d88dc62a47 100644 --- a/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts +++ b/packages/backend-app-api/src/services/implementations/auth/external/jwks.ts @@ -16,8 +16,11 @@ import { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose'; import { Config } from '@backstage/config'; -import { readStringOrStringArrayFromConfig } from './helpers'; -import { TokenHandler } from './types'; +import { + readAccessRestrictionsFromConfig, + readStringOrStringArrayFromConfig, +} from './helpers'; +import { AccessRestriptionsMap, TokenHandler } from './types'; /** * Handles `type: jwks` access. @@ -32,20 +35,30 @@ export class JWKSHandler implements TokenHandler { subjectPrefix?: string; url: URL; jwks: JWTVerifyGetKey; + allAccessRestrictions?: AccessRestriptionsMap; }> = []; - add(options: Config) { - const algorithms = readStringOrStringArrayFromConfig(options, 'algorithm'); - const issuers = readStringOrStringArrayFromConfig(options, 'issuer'); - const audiences = readStringOrStringArrayFromConfig(options, 'audience'); - const subjectPrefix = options.getOptionalString('subjectPrefix'); - const url = new URL(options.getString('url')); - const jwks = createRemoteJWKSet(url); - - if (!options.getString('url').match(/^\S+$/)) { - throw new Error('Illegal URL, must be a set of non-space characters'); + add(config: Config) { + if (!config.getString('options.url').match(/^\S+$/)) { + throw new Error( + 'Illegal JWKS URL, must be a set of non-space characters', + ); } + const algorithms = readStringOrStringArrayFromConfig( + config, + 'options.algorithm', + ); + const issuers = readStringOrStringArrayFromConfig(config, 'options.issuer'); + const audiences = readStringOrStringArrayFromConfig( + config, + 'options.audience', + ); + const subjectPrefix = config.getOptionalString('options.subjectPrefix'); + const url = new URL(config.getString('options.url')); + const jwks = createRemoteJWKSet(url); + const allAccessRestrictions = readAccessRestrictionsFromConfig(config); + this.#entries.push({ algorithms, audiences, @@ -53,6 +66,7 @@ export class JWKSHandler implements TokenHandler { jwks, subjectPrefix, url, + allAccessRestrictions, }); } @@ -68,11 +82,13 @@ export class JWKSHandler implements TokenHandler { }); if (sub) { - if (entry.subjectPrefix) { - return { subject: `external:${entry.subjectPrefix}:${sub}` }; - } - - return { subject: `external:${sub}` }; + const prefix = entry.subjectPrefix + ? `external:${entry.subjectPrefix}:` + : 'external:'; + return { + subject: `${prefix}${sub}`, + allAccessRestrictions: entry.allAccessRestrictions, + }; } } catch { continue; From d7cdf979c8800f8a8037499fe4f3f3f2431aa3cf Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 24 May 2024 11:14:47 +0200 Subject: [PATCH 529/567] Move @backstage/repo-tools to devDependencies Signed-off-by: Marcus --- plugins/search-backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 523abc24c9..40371682fc 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -58,7 +58,6 @@ "@backstage/plugin-permission-node": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-search-common": "workspace:^", - "@backstage/repo-tools": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "dataloader": "^2.0.0", @@ -72,6 +71,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/repo-tools": "workspace:^", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, From 34dc47d1943b603470e36126acede8785f7bd780 Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 24 May 2024 11:16:05 +0200 Subject: [PATCH 530/567] Add changeset Signed-off-by: Marcus --- .changeset/shaggy-jokes-promise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaggy-jokes-promise.md diff --git a/.changeset/shaggy-jokes-promise.md b/.changeset/shaggy-jokes-promise.md new file mode 100644 index 0000000000..e7788b69f4 --- /dev/null +++ b/.changeset/shaggy-jokes-promise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +Move @backstage/repo-tools to devDependencies From c00f7ee0f294931139920b656df42df2b86ab246 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 24 May 2024 15:13:48 +0200 Subject: [PATCH 531/567] chore: added changeset Signed-off-by: blam --- .changeset/gold-teachers-wink.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gold-teachers-wink.md diff --git a/.changeset/gold-teachers-wink.md b/.changeset/gold-teachers-wink.md new file mode 100644 index 0000000000..0578e8d02b --- /dev/null +++ b/.changeset/gold-teachers-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix issue with `esm` loaded dependencies being different from the `cjs` import for Vite dependencies From eff06359a8299298e632087ccefaf3f0434b5f8d Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Fri, 24 May 2024 17:05:49 +0200 Subject: [PATCH 532/567] feat: add scaffolder action to trigger gitlab pipelines Signed-off-by: ElaineDeMattosSilvaB --- .../actions/gitlabPipelineTrigger.examples.ts | 41 +++++++ .../src/actions/gitlabPipelineTrigger.ts | 102 ++++++++++++++++++ .../src/actions/index.ts | 7 +- .../src/module.ts | 4 +- 4 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts new file mode 100644 index 0000000000..4948db908b --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; +import { commonGitlabConfigExample } from '../commonGitlabConfig'; + +export const examples: TemplateExample[] = [ + { + description: 'Trigger a GitLab Project Pipeline', + example: yaml.stringify({ + steps: [ + { + id: 'triggerPipeline', + name: 'Trigger Project Pipeline', + action: 'gitlab:pipeline:trigger', + input: { + ...commonGitlabConfigExample, + projectId: 12, + tokenDescription: + 'This is the text that will appear in the pipeline token', + token: 'glpt-xxxxxxxxxxxx', + branch: 'main', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts new file mode 100644 index 0000000000..2edb529ea7 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -0,0 +1,102 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + ExpandedPipelineSchema, + PipelineTriggerTokenSchema, +} from '@gitbeaker/rest'; +import { z } from 'zod'; +import commonGitlabConfig from '../commonGitlabConfig'; +import { getClient, parseRepoUrl } from '../util'; +import { examples } from './gitlabPipelineTrigger.examples'; + +const pipelineInputProperties = z.object({ + projectId: z.number().describe('Project Id'), + tokenDescription: z.string().describe('Pipeline token description'), + branch: z.string().describe('Project branch'), +}); + +const pipelineOutputProperties = z.object({ + pipelineUrl: z.string({ description: 'Pipeline Url' }), +}); + +/** + * Creates a `gitlab:pipeline:trigger` Scaffolder action. + * + * @param options - Templating configuration. + * @public + */ +export const createTriggerGitlabPipelineAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + return createTemplateAction({ + id: 'gitlab:pipeline:trigger', + description: 'Triggers a GitLab Pipeline.', + examples, + schema: { + input: commonGitlabConfig.merge(pipelineInputProperties), + output: pipelineOutputProperties, + }, + async handler(ctx) { + try { + const { repoUrl, projectId, tokenDescription, token, branch } = + commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + + const { host } = parseRepoUrl(repoUrl, integrations); + const api = getClient({ host, integrations, token }); + + // Get a pipeline token + const createdPipelineTokenResponse = + (await api.PipelineTriggerTokens.create( + projectId, + tokenDescription, + )) as PipelineTriggerTokenSchema; + + if (!createdPipelineTokenResponse.token) { + return; + } + // Use the pipeline token to trigger the pipeline in the project + const pipelineTriggerResponse = + (await api.PipelineTriggerTokens.trigger( + projectId, + branch, + createdPipelineTokenResponse.token, + )) as ExpandedPipelineSchema; + + // Delete the pipeline token + await api.PipelineTriggerTokens.remove( + projectId, + createdPipelineTokenResponse.id, + ); + + ctx.output('pipelineUrl', pipelineTriggerResponse.web_url); + } catch (error: any) { + if (error instanceof z.ZodError) { + // Handling Zod validation errors + throw new InputError(`Validation error: ${error.message}`, { + validationErrors: error.errors, + }); + } + // Handling other errors + throw new InputError(`Failed to trigger Pipeline: ${error.message}`); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts index 4a5ca06c98..fb80a43ff4 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts @@ -14,10 +14,11 @@ * limitations under the License. */ export * from './createGitlabGroupEnsureExistsAction'; -export * from './createGitlabProjectDeployTokenAction'; -export * from './createGitlabProjectAccessTokenAction'; -export * from './createGitlabProjectVariableAction'; export * from './createGitlabIssueAction'; +export * from './createGitlabProjectAccessTokenAction'; +export * from './createGitlabProjectDeployTokenAction'; +export * from './createGitlabProjectVariableAction'; export * from './gitlab'; export * from './gitlabMergeRequest'; export * from './gitlabRepoPush'; +export * from './gitlabPipelineTrigger'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/module.ts b/plugins/scaffolder-backend-module-gitlab/src/module.ts index 3571575827..17052b987f 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/module.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/module.ts @@ -17,6 +17,7 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; +import { ScmIntegrations } from '@backstage/integration'; import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; import { createGitlabGroupEnsureExistsAction, @@ -27,8 +28,8 @@ import { createGitlabRepoPushAction, createPublishGitlabAction, createPublishGitlabMergeRequestAction, + createTriggerGitlabPipelineAction, } from './actions'; -import { ScmIntegrations } from '@backstage/integration'; /** * @public @@ -55,6 +56,7 @@ export const gitlabModule = createBackendModule({ createGitlabRepoPushAction({ integrations }), createPublishGitlabAction({ config, integrations }), createPublishGitlabMergeRequestAction({ integrations }), + createTriggerGitlabPipelineAction({ integrations }), ); }, }); From 788eca7addd3375c2adb60327fe87382e550cdcf Mon Sep 17 00:00:00 2001 From: Stephen Glass Date: Fri, 24 May 2024 23:42:14 -0400 Subject: [PATCH 533/567] fix readme for new plugins created using cli Signed-off-by: Stephen Glass --- .changeset/eighty-yaks-switch.md | 5 +++++ packages/cli/templates/default-backend-plugin/README.md.hbs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/eighty-yaks-switch.md diff --git a/.changeset/eighty-yaks-switch.md b/.changeset/eighty-yaks-switch.md new file mode 100644 index 0000000000..30e3374d70 --- /dev/null +++ b/.changeset/eighty-yaks-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fix readme for new plugins created using cli diff --git a/packages/cli/templates/default-backend-plugin/README.md.hbs b/packages/cli/templates/default-backend-plugin/README.md.hbs index e95c626167..366ed27104 100644 --- a/packages/cli/templates/default-backend-plugin/README.md.hbs +++ b/packages/cli/templates/default-backend-plugin/README.md.hbs @@ -7,7 +7,7 @@ _This plugin was created through the Backstage CLI_ ## Getting started Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn -start` in the root directory, and then navigating to [/{{pluginVar}}/health](http://localhost:7007/api/{{pluginVar}}/health). +start` in the root directory, and then navigating to [/{{id}}/health](http://localhost:7007/api/{{id}}/health). You can also serve the plugin in isolation by running `yarn start` in the plugin directory. This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. From 1354d81b86f74d9c173dee15b96a3cc5f46cc8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 25 May 2024 11:38:55 +0200 Subject: [PATCH 534/567] use node-fetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/nice-pants-shave.md | 8 +++ .../src/helpers.ts | 1 + plugins/notifications-node/package.json | 1 + .../DefaultNotificationService.test.ts | 58 +++++++++---------- .../src/service/DefaultNotificationService.ts | 3 +- .../src/actions/gitea.ts | 1 + .../package.json | 1 + .../src/actions/createProject.ts | 1 + yarn.lock | 2 + 9 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 .changeset/nice-pants-shave.md diff --git a/.changeset/nice-pants-shave.md b/.changeset/nice-pants-shave.md new file mode 100644 index 0000000000..500888a817 --- /dev/null +++ b/.changeset/nice-pants-shave.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-notifications-node': patch +--- + +Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 diff --git a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts index 60411e53d5..c1a14ae6b4 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts +++ b/plugins/auth-backend-module-cloudflare-access-provider/src/helpers.ts @@ -23,6 +23,7 @@ import { } from '@backstage/errors'; import express from 'express'; import { createRemoteJWKSet, jwtVerify } from 'jose'; +import fetch, { Headers } from 'node-fetch'; import { CACHE_PREFIX, CF_JWT_HEADER, diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 1f3760f5c6..2500026f3b 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -37,6 +37,7 @@ "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "knex": "^3.0.0", + "node-fetch": "^2.6.7", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/plugins/notifications-node/src/service/DefaultNotificationService.test.ts b/plugins/notifications-node/src/service/DefaultNotificationService.test.ts index 4711f3de18..d5c327e788 100644 --- a/plugins/notifications-node/src/service/DefaultNotificationService.test.ts +++ b/plugins/notifications-node/src/service/DefaultNotificationService.test.ts @@ -24,8 +24,6 @@ import { } from './DefaultNotificationService'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; -const server = setupServer(); - const testNotification: NotificationPayload = { title: 'Notification 1', link: '/catalog', @@ -33,8 +31,12 @@ const testNotification: NotificationPayload = { }; describe('DefaultNotificationService', () => { + const server = setupServer(); setupRequestMockHandlers(server); - const discovery = mockServices.discovery(); + + const discovery = mockServices.discovery.mock({ + getBaseUrl: jest.fn().mockResolvedValue('http://example.com'), + }); const auth = mockServices.auth(); let service: DefaultNotificationService; @@ -53,20 +55,17 @@ describe('DefaultNotificationService', () => { }; server.use( - rest.post( - `${await discovery.getBaseUrl('notifications')}/`, - async (req, res, ctx) => { - const json = await req.json(); - expect(json).toEqual(body); - expect(req.headers.get('Authorization')).toBe( - mockCredentials.service.header({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: 'notifications', - }), - ); - return res(ctx.status(200)); - }, - ), + rest.post('http://example.com', async (req, res, ctx) => { + const json = await req.json(); + expect(json).toEqual(body); + expect(req.headers.get('Authorization')).toBe( + mockCredentials.service.header({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'notifications', + }), + ); + return res(ctx.status(200)); + }), ); await expect(service.send(body)).resolves.toBeUndefined(); }); @@ -78,20 +77,17 @@ describe('DefaultNotificationService', () => { }; server.use( - rest.post( - `${await discovery.getBaseUrl('notifications')}/`, - async (req, res, ctx) => { - const json = await req.json(); - expect(json).toEqual(body); - expect(req.headers.get('Authorization')).toBe( - mockCredentials.service.header({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: 'notifications', - }), - ); - return res(ctx.status(400)); - }, - ), + rest.post('http://example.com', async (req, res, ctx) => { + const json = await req.json(); + expect(json).toEqual(body); + expect(req.headers.get('Authorization')).toBe( + mockCredentials.service.header({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'notifications', + }), + ); + return res(ctx.status(400)); + }), ); await expect(service.send(body)).rejects.toThrow( 'Request failed with status 400', diff --git a/plugins/notifications-node/src/service/DefaultNotificationService.ts b/plugins/notifications-node/src/service/DefaultNotificationService.ts index 950f42ae83..7a46e01689 100644 --- a/plugins/notifications-node/src/service/DefaultNotificationService.ts +++ b/plugins/notifications-node/src/service/DefaultNotificationService.ts @@ -17,6 +17,7 @@ import { NotificationService } from './NotificationService'; import { AuthService, DiscoveryService } from '@backstage/backend-plugin-api'; import { NotificationPayload } from '@backstage/plugin-notifications-common'; +import fetch from 'node-fetch'; /** @public */ export type NotificationServiceOptions = { @@ -68,7 +69,7 @@ export class DefaultNotificationService implements NotificationService { targetPluginId: 'notifications', }); - const response = await fetch(`${baseUrl}/`, { + const response = await fetch(baseUrl, { method: 'POST', body: JSON.stringify(notification), headers: { diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 6b29ff2a4c..601a670237 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -30,6 +30,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import { examples } from './gitea.examples'; import crypto from 'crypto'; +import fetch, { Response, RequestInit } from 'node-fetch'; const checkGiteaContentUrl = async ( config: GiteaIntegrationConfig, diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index a44de6705a..861e78b565 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -44,6 +44,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", + "node-fetch": "^2.6.7", "yaml": "^2.3.3" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 259b670f9e..686053b178 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -17,6 +17,7 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; +import fetch from 'node-fetch'; /** * Creates the `sentry:project:create` Scaffolder action. diff --git a/yarn.lock b/yarn.lock index fde9aa7d1f..214d43ecfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6259,6 +6259,7 @@ __metadata: "@backstage/test-utils": "workspace:^" knex: ^3.0.0 msw: ^1.0.0 + node-fetch: ^2.6.7 uuid: ^9.0.0 languageName: unknown linkType: soft @@ -6751,6 +6752,7 @@ __metadata: "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" msw: ^2.0.0 + node-fetch: ^2.6.7 yaml: ^2.3.3 languageName: unknown linkType: soft From debcc8c8d375605cad53116cf4242f82ef059c23 Mon Sep 17 00:00:00 2001 From: David Weber Date: Sun, 26 May 2024 21:06:29 +0200 Subject: [PATCH 535/567] feat: migrate LDAP catalog module to the new backend system Signed-off-by: David Weber --- .changeset/spotty-plants-switch.md | 5 + .../building-backends/08-migrating.md | 2 +- docs/integrations/ldap/org--old.md | 417 ++++++++++++++++++ docs/integrations/ldap/org.md | 223 +++------- plugins/catalog-backend-module-ldap/README.md | 4 + .../catalog-backend-module-ldap/api-report.md | 52 ++- .../catalog-backend-module-ldap/config.d.ts | 231 +++++++++- .../catalog-backend-module-ldap/src/index.ts | 5 + .../src/ldap/config.test.ts | 288 ++++++------ .../src/ldap/config.ts | 338 ++++++++------ .../src/ldap/index.ts | 2 +- .../catalog-backend-module-ldap/src/module.ts | 114 +++++ .../src/processors/LdapOrgEntityProvider.ts | 123 +++++- .../src/processors/LdapOrgReaderProcessor.ts | 4 +- .../src/processors/index.ts | 5 +- 15 files changed, 1358 insertions(+), 455 deletions(-) create mode 100644 .changeset/spotty-plants-switch.md create mode 100644 docs/integrations/ldap/org--old.md create mode 100644 plugins/catalog-backend-module-ldap/src/module.ts diff --git a/.changeset/spotty-plants-switch.md b/.changeset/spotty-plants-switch.md new file mode 100644 index 0000000000..a5951de05d --- /dev/null +++ b/.changeset/spotty-plants-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': minor +--- + +Migrate LDAP catalog module to the new backend system. diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index ae41193b11..d38acb695e 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -1369,7 +1369,7 @@ The vast majority of the backend plugins that currently live in the Backstage Re | @backstage/plugin-catalog-backend-module-github-org | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-github-org/README.md) | | @backstage/plugin-catalog-backend-module-gitlab | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-gitlab/README.md) | | @backstage/plugin-catalog-backend-module-incremental-ingestion | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-incremental-ingestion/README.md) | -| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) | +| @backstage/plugin-catalog-backend-module-ldap | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-ldap/README.md) | | @backstage/plugin-catalog-backend-module-msgraph | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md) | | @backstage/plugin-catalog-backend-module-openapi | backend-plugin-module | true | | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-openapi/README.md) | | @backstage/plugin-catalog-backend-module-puppetdb | backend-plugin-module | true | true | [README](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-puppetdb/README.md) | diff --git a/docs/integrations/ldap/org--old.md b/docs/integrations/ldap/org--old.md new file mode 100644 index 0000000000..13fbcb9408 --- /dev/null +++ b/docs/integrations/ldap/org--old.md @@ -0,0 +1,417 @@ +--- +id: org--old +title: LDAP Organizational Data +sidebar_label: Org Data +# prettier-ignore +description: Setting up ingestion of organizational data from LDAP +--- + +The Backstage catalog can be set up to ingest organizational data - users and +groups - directly from an LDAP compatible service. The result is a hierarchy of +[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and +[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind +entities that mirror your org setup. + +## Supported vendors + +Backstage in general supports OpenLDAP compatible vendors, as well as Active Directory and FreeIPA. If you are using a vendor that does not seem to be supported, please [file an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md). + +## Installation + +This guide will use the Entity Provider method. If you for some reason prefer +the Processor method (not recommended), it is described separately below. + +The provider is not installed by default, therefore you have to add a dependency +to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap +``` + +:::note Note + +When configuring to use a Provider instead of a Processor you do not +need to add a _location_ pointing to your LDAP server + +::: + +Update the catalog plugin initialization in your backend to add the provider and +schedule it: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + + /* highlight-add-start */ + // The target parameter below needs to match the ldap.providers.target + // value specified in your app-config. + builder.addEntityProvider( + LdapOrgEntityProvider.fromConfig(env.config, { + id: 'our-ldap-master', + target: 'ldaps://ds.example.net', + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 60 }, + timeout: { minutes: 15 }, + }), + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +After this, you also have to add some configuration in your app-config that +describes what you want to import for that target. + +## Configuration + +The following configuration is a small example of how a setup could look for +importing groups and users from a corporate LDAP server. + +```yaml +ldap: + providers: + - target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: ${LDAP_SECRET} + users: + dn: ou=people,ou=example,dc=example,dc=net + options: + filter: (uid=*) + map: + description: l + set: + metadata.customField: 'hello' + groups: + dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' +``` + +There may be many providers, each targeting a specific `target` which is +supposed to match the `target` of a dedicated provider instance - i.e., you will +add one entity provider class instance per target to ingest from. + +These config blocks have a lot of options in them, so we will describe each +"root" key within the block separately. + +### target + +This is the URL of the targeted server, typically on the form +`ldaps://ds.example.net` for SSL enabled servers or `ldap://ds.example.net` +without SSL. + +#### target.tls.keys + +`keys` in TLS options specifies location of a file, that contains private keys +to establish connection with your LDAP server, in PEM format. See an example +for Google Secure LDAP Service below. + +#### target.tls.certs + +`certs` in TLS options specifies location of a file, that contains certificate +chains to establish connection with your LDAP server, in PEM format. See an +example for Google Secure LDAP Service below. + +### bind + +The bind block specifies how the plugin should bind (essentially, to +authenticate) towards the server. It has the following fields. + +```yaml +dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net +secret: ${LDAP_SECRET} +``` + +The `dn` is the full LDAP Distinguished Name for the user that the plugin +authenticates itself as. At this point, only regular user based authentication +is supported. + +The `secret` is the password of the same user. In this example, it is given in +the form of an environment variable `LDAP_SECRET`, that has to be set when the +backend starts. + +### users + +The `users` block defines the settings that govern the reading and +interpretation of users. Its fields are explained in separate sections below. + +#### users.dn + +The DN under which users are stored, e.g. +`ou=people,ou=example,dc=example,dc=net`. + +#### users.options + +The search options to use when sending the query to the server, when reading all +users. All the options are shown below, with their default values, but they are +all optional. + +```yaml +options: + # One of 'base', 'one', or 'sub'. + scope: one + # The filter is the one that you commonly will want to specify explicitly. It + # is a string on the standard LDAP query format. Use it to select out the set + # of users that are of actual interest to ingest. For example, you may want + # to filter out disabled users. + filter: (uid=*) + # The attribute selectors for each item, as passed to the LDAP server. + attributes: ['*', '+'] + # This field is either 'false' to disable paging when reading from the + # server, or an object on the form '{ pageSize: 100, pagePause: true }' that + # specifies the details of how the paging shall work. + paged: false +``` + +#### users.set + +This optional piece lets you specify a number of JSON paths (on a.b.c form) and +hard coded values to set on those paths. This can be useful for example if you +want to hard code a namespace or similar on the generated entities. + +```yaml +set: + # Just an example; the key and value can be anything + metadata.namespace: 'ldap' +``` + +#### users.map + +Mappings from well known entity fields, to LDAP attribute names. This is where +you are able to define how to interpret the attributes of each LDAP result item, +and to move them into the corresponding entity fields. All the options are shown +below, with their default values, but they are all optional. + +If you leave out an optional mapping, it will still be copied using that default +value. For example, even if you do not put in the field `displayName` in your +config, the provider will still copy the attribute `cn` into the entity field +`spec.profile.displayName`. + +```yaml +map: + # The name of the attribute that holds the relative + # distinguished name of each entry. + rdn: uid + # The name of the attribute that shall be used for the value of + # the metadata.name field of the entity. + name: uid + # The name of the attribute that shall be used for the value of + # the metadata.description field of the entity. + description: description + # The name of the attribute that shall be used for the value of + # the spec.profile.displayName field of the entity. + displayName: cn + # The name of the attribute that shall be used for the value of + # the spec.profile.email field of the entity. + email: mail + # The name of the attribute that shall be used for the value of + # the spec.profile.picture field of the entity. + picture: + # The name of the attribute that shall be used for the values of + # the spec.memberOf field of the entity. + memberOf: memberOf +``` + +### groups + +The `groups` block defines the settings that govern the reading and +interpretation of groups. Its fields are explained in separate sections below. + +#### groups.dn + +The DN under which groups are stored, e.g. +`ou=people,ou=example,dc=example,dc=net`. + +#### groups.options + +The search options to use when sending the query to the server, when reading all +groups. All the options are shown below, with their default values, but they are +all optional. + +```yaml +options: + # One of 'base', 'one', or 'sub'. + scope: one + # The filter is the one that you commonly will want to specify explicitly. It + # is a string on the standard LDAP query format. Use it to select out the set + # of groups that are of actual interest to ingest. For example, you may want + # to filter out disabled groups. + filter: (&(objectClass=some-group-class)(!(groupType=email))) + # The attribute selectors for each item, as passed to the LDAP server. + attributes: ['*', '+'] + # This field is either 'false' to disable paging when reading from the + # server, or an object on the form '{ pageSize: 100, pagePause: true }' that + # specifies the details of how the paging shall work. + paged: false +``` + +#### groups.set + +This optional piece lets you specify a number of JSON paths (on a.b.c form) and +hard coded values to set on those paths. This can be useful for example if you +want to hard code a namespace or similar on the generated entities. + +```yaml +set: + # Just an example; the key and value can be anything + metadata.namespace: 'ldap' +``` + +#### groups.map + +Mappings from well known entity fields, to LDAP attribute names. This is where +you are able to define how to interpret the attributes of each LDAP result item, +and to move them into the corresponding entity fields. All of the options are +shown below, with their default values, but they are all optional. + +If you leave out an optional mapping, it will still be copied using that default +value. For example, even if you do not put in the field `displayName` in your +config, the provider will still copy the attribute `cn` into the entity field +`spec.profile.displayName`. If the target field is optional, such as the display +name, the importer will accept missing attributes and just leave the target +field unset. If the target field is mandatory, such as the name of the entity, +validation will fail if the source attribute is missing. + +```yaml +map: + # The name of the attribute that holds the relative + # distinguished name of each entry. This value is copied into a + # well known annotation to be able to query by it later. + rdn: cn + # The name of the attribute that shall be used for the value of + # the metadata.name field of the entity. + name: cn + # The name of the attribute that shall be used for the value of + # the metadata.description field of the entity. + description: description + # The name of the attribute that shall be used for the value of + # the spec.type field of the entity. + type: groupType + # The name of the attribute that shall be used for the value of + # the spec.profile.displayName field of the entity. + displayName: cn + # The name of the attribute that shall be used for the value of + # the spec.profile.email field of the entity. + email: + # The name of the attribute that shall be used for the value of + # the spec.profile.picture field of the entity. + picture: + # The name of the attribute that shall be used for the values of + # the spec.parent field of the entity. + memberOf: memberOf + # The name of the attribute that shall be used for the values of + # the spec.children field of the entity. + members: member +``` + +## Customize the Provider + +In case you want to customize the ingested entities, the provider allows to pass +transformers for users and groups. Here we will show an example of overriding +the group transformer. + +1. Create a transformer: + + ```ts + export async function myGroupTransformer( + vendor: LdapVendor, + config: GroupConfig, + group: SearchEntry, + ): Promise { + // Transformations may change namespace, change entity naming pattern, fill + // profile with more or other details... + + // Create the group entity on your own, or wrap the default transformer + return await defaultGroupTransformer(vendor, config, group); + } + ``` + +2. Configure the provider with the transformer: + + ```ts + const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, { + id: 'our-ldap-master', + target: 'ldaps://ds.example.net', + logger: env.logger, + groupTransformer: myGroupTransformer, + }); + ``` + +## Using a Processor instead of a Provider + +An alternative to using the Provider for ingesting LDAP entries is to use a +Processor. This is the old way that's based on registering locations with the +proper type and target, triggering the processor to run. + +The drawback of this method is that it will leave orphaned Group/User entities +whenever they are deleted on your LDAP server, and you cannot control the +frequency with which they are refreshed, separately from other processors. + +### Processor Installation + +The `LdapOrgReaderProcessor` is not registered by default, so you have to +register it in the catalog plugin: + +```typescript title="packages/backend/src/plugins/catalog.ts" +builder.addProcessor( + LdapOrgReaderProcessor.fromConfig(env.config, { + logger: env.logger, + }), +); +``` + +### Driving LDAP Org Processor Ingestion with Locations + +Locations point out the specific org(s) you want to import. The `type` of these +locations must be `ldap-org`, and the `target` must point to the exact URL +(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can +have several such location entries if you want, but typically you will have just +one. + +```yaml +catalog: + locations: + - type: ldap-org + target: ldaps://ds.example.net + rules: + - allow: [User, Group] +``` + +### Example configurations + +#### Google Secure LDAP Service + +To sync Google Workspace/Cloud Identity organization data to users and groups in backstage, +you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first. + +Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration, +as mentioned below. `keys` and `certs` specify the location of files that are generated +while configuring Secure LDAP Service above. + +```yaml +ldap: + providers: + - target: ldaps://ldap.google.com:636 + tls: + rejectUnauthorized: false + keys: '/var/secrets/tls/gldap.key' + certs: '/var/secrets/tls/gldap.crt' + users: + # users configuration comes here + groups: + # groups configuration comes here +``` diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index a10bc3918c..f70d02afed 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -18,9 +18,6 @@ Backstage in general supports OpenLDAP compatible vendors, as well as Active Dir ## Installation -This guide will use the Entity Provider method. If you for some reason prefer -the Processor method (not recommended), it is described separately below. - The provider is not installed by default, therefore you have to add a dependency to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. @@ -29,47 +26,30 @@ to `@backstage/plugin-catalog-backend-module-ldap` to your backend package. yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-ldap ``` -:::note Note +Next add the basic configuration to `app-config.yaml` -When configuring to use a Provider instead of a Processor you do not -need to add a _location_ pointing to your LDAP server - -::: - -Update the catalog plugin initialization in your backend to add the provider and -schedule it: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { LdapOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-ldap'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The target parameter below needs to match the ldap.providers.target - // value specified in your app-config. - builder.addEntityProvider( - LdapOrgEntityProvider.fromConfig(env.config, { - id: 'our-ldap-master', - target: 'ldaps://ds.example.net', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} +```yaml title="app-config.yaml" +catalog: + providers: + ldapOrg: + default: + target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: ${LDAP_SECRET} + schedule: + frequency: PT1H + timeout: PT15M ``` -After this, you also have to add some configuration in your app-config that -describes what you want to import for that target. +Finally, updated your backend by adding the following line: + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-ldap')); +/* highlight-add-end */ +``` ## Configuration @@ -77,34 +57,32 @@ The following configuration is a small example of how a setup could look for importing groups and users from a corporate LDAP server. ```yaml -ldap: +catalog: providers: - - target: ldaps://ds.example.net - bind: - dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net - secret: ${LDAP_SECRET} - users: - dn: ou=people,ou=example,dc=example,dc=net - options: - filter: (uid=*) - map: - description: l - set: - metadata.customField: 'hello' - groups: - dn: ou=access,ou=groups,ou=example,dc=example,dc=net - options: - filter: (&(objectClass=some-group-class)(!(groupType=email))) - map: - description: l - set: - metadata.customField: 'hello' + ldapOrg: + default: + target: ldaps://ds.example.net + bind: + dn: uid=ldap-reader-user,ou=people,ou=example,dc=example,dc=net + secret: ${LDAP_SECRET} + users: + dn: ou=people,ou=example,dc=example,dc=net + options: + filter: (uid=*) + map: + description: l + set: + metadata.customField: 'hello' + groups: + dn: ou=access,ou=groups,ou=example,dc=example,dc=net + options: + filter: (&(objectClass=some-group-class)(!(groupType=email))) + map: + description: l + set: + metadata.customField: 'hello' ``` -There may be many providers, each targeting a specific `target` which is -supposed to match the `target` of a dedicated provider instance - i.e., you will -add one entity provider class instance per target to ingest from. - These config blocks have a lot of options in them, so we will describe each "root" key within the block separately. @@ -321,97 +299,34 @@ map: ## Customize the Provider In case you want to customize the ingested entities, the provider allows to pass -transformers for users and groups. Here we will show an example of overriding -the group transformer. +transformers for users and groups. -1. Create a transformer: +Transformers can be configured by extending `ldapOrgEntityProviderTransformExtensionPoint`. Here is an example: - ```ts - export async function myGroupTransformer( - vendor: LdapVendor, - config: GroupConfig, - group: SearchEntry, - ): Promise { - // Transformations may change namespace, change entity naming pattern, fill - // profile with more or other details... +```ts title="packages/backend/src/index.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { ldapOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-ldap'; +import { myUserTransformer, myGroupTransformer } from './transformers'; - // Create the group entity on your own, or wrap the default transformer - return await defaultGroupTransformer(vendor, config, group); - } - ``` - -2. Configure the provider with the transformer: - - ```ts - const ldapEntityProvider = LdapOrgEntityProvider.fromConfig(env.config, { - id: 'our-ldap-master', - target: 'ldaps://ds.example.net', - logger: env.logger, - groupTransformer: myGroupTransformer, - }); - ``` - -## Using a Processor instead of a Provider - -An alternative to using the Provider for ingesting LDAP entries is to use a -Processor. This is the old way that's based on registering locations with the -proper type and target, triggering the processor to run. - -The drawback of this method is that it will leave orphaned Group/User entities -whenever they are deleted on your LDAP server, and you cannot control the -frequency with which they are refreshed, separately from other processors. - -### Processor Installation - -The `LdapOrgReaderProcessor` is not registered by default, so you have to -register it in the catalog plugin: - -```typescript title="packages/backend/src/plugins/catalog.ts" -builder.addProcessor( - LdapOrgReaderProcessor.fromConfig(env.config, { - logger: env.logger, +backend.add( + createBackendModule({ + pluginId: 'catalog', + moduleId: 'ldap-extensions', + register(env) { + env.registerInit({ + deps: { + /* highlight-add-start */ + ldapTransformers: ldapOrgEntityProviderTransformExtensionPoint, + /* highlight-add-end */ + }, + async init({ ldapTransformers }) { + /* highlight-add-start */ + ldapTransformers.setUserTransformer(myUserTransformer); + ldapTransformers.setGroupTransformer(myGroupTransformer); + /* highlight-add-end */ + }, + }); + }, }), ); ``` - -### Driving LDAP Org Processor Ingestion with Locations - -Locations point out the specific org(s) you want to import. The `type` of these -locations must be `ldap-org`, and the `target` must point to the exact URL -(starting with `ldap://` or `ldaps://`) of the targeted LDAP server. You can -have several such location entries if you want, but typically you will have just -one. - -```yaml -catalog: - locations: - - type: ldap-org - target: ldaps://ds.example.net - rules: - - allow: [User, Group] -``` - -### Example configurations - -#### Google Secure LDAP Service - -To sync Google Workspace/Cloud Identity organization data to users and groups in backstage, -you must [configure Secure LDAP Service](https://support.google.com/a/answer/9048516) first. - -Once Secure LDAP Service is configured, you can enable TLS options in LDAP configuration, -as mentioned below. `keys` and `certs` specify the location of files that are generated -while configuring Secure LDAP Service above. - -```yaml -ldap: - providers: - - target: ldaps://ldap.google.com:636 - tls: - rejectUnauthorized: false - keys: '/var/secrets/tls/gldap.key' - certs: '/var/secrets/tls/gldap.crt' - users: - # users configuration comes here - groups: - # groups configuration comes here -``` diff --git a/plugins/catalog-backend-module-ldap/README.md b/plugins/catalog-backend-module-ldap/README.md index 2bc34ba949..6fd05dda3b 100644 --- a/plugins/catalog-backend-module-ldap/README.md +++ b/plugins/catalog-backend-module-ldap/README.md @@ -8,3 +8,7 @@ groups from your Active Directory or another LDAP compatible server. See [Backstage documentation](https://backstage.io/docs/integrations/ldap/org) for details on how to install and configure the plugin. + +## Legacy backend + +You can find the legacy documentation at `docs/integrations/ldap/org--old.md`. diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index 76276c8fad..5fcbbc3091 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -3,20 +3,26 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { Client } from 'ldapjs'; import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { GroupEntity } from '@backstage/catalog-model'; +import { GroupTransformer as GroupTransformer_2 } from '@backstage/plugin-catalog-backend-module-ldap'; import { JsonValue } from '@backstage/types'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { SearchEntry } from 'ldapjs'; import { SearchOptions } from 'ldapjs'; import { TaskRunner } from '@backstage/backend-tasks'; +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { UserEntity } from '@backstage/catalog-model'; +import { UserTransformer as UserTransformer_2 } from '@backstage/plugin-catalog-backend-module-ldap'; // @public export type BindConfig = { @@ -24,6 +30,10 @@ export type BindConfig = { secret: string; }; +// @public +const catalogModuleLdapOrgEntityProvider: () => BackendFeature; +export default catalogModuleLdapOrgEntityProvider; + // @public export function defaultGroupTransformer( vendor: LdapVendor, @@ -109,14 +119,19 @@ export class LdapOrgEntityProvider implements EntityProvider { static fromConfig( configRoot: Config, options: LdapOrgEntityProviderOptions, + ): LdapOrgEntityProvider[]; + // (undocumented) + static fromLegacyConfig( + configRoot: Config, + options: LdapOrgEntityProviderLegacyOptions, ): LdapOrgEntityProvider; // (undocumented) getProviderName(): string; read(options?: { logger?: LoggerService }): Promise; } -// @public -export interface LdapOrgEntityProviderOptions { +// @public @deprecated +export interface LdapOrgEntityProviderLegacyOptions { groupTransformer?: GroupTransformer; id: string; logger: LoggerService; @@ -125,6 +140,30 @@ export interface LdapOrgEntityProviderOptions { userTransformer?: UserTransformer; } +// @public +export type LdapOrgEntityProviderOptions = + | LdapOrgEntityProviderLegacyOptions + | { + logger: LoggerService; + schedule?: 'manual' | TaskRunner; + scheduler?: PluginTaskScheduler; + userTransformer?: UserTransformer | Record; + groupTransformer?: GroupTransformer | Record; + }; + +// @public +export interface LdapOrgEntityProviderTransformsExtensionPoint { + setGroupTransformer( + transformer: GroupTransformer_2 | Record, + ): void; + setUserTransformer( + transformer: UserTransformer_2 | Record, + ): void; +} + +// @public +export const ldapOrgEntityProviderTransformsExtensionPoint: ExtensionPoint; + // @public export class LdapOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -154,11 +193,13 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { // @public export type LdapProviderConfig = { + id: string; target: string; tls?: TLSConfig; bind?: BindConfig; users: UserConfig; groups: GroupConfig; + schedule?: TaskScheduleDefinition; }; // @public @@ -176,8 +217,8 @@ export function mapStringAttr( setter: (value: string) => void, ): void; -// @public -export function readLdapConfig(config: Config): LdapProviderConfig[]; +// @public @deprecated +export function readLdapLegacyConfig(config: Config): LdapProviderConfig[]; // @public export function readLdapOrg( @@ -194,6 +235,9 @@ export function readLdapOrg( groups: GroupEntity[]; }>; +// @public +export function readProviderConfigs(config: Config): LdapProviderConfig[]; + // @public export type TLSConfig = { rejectUnauthorized?: boolean; diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index eb9564f20d..a585471033 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -19,6 +19,8 @@ import { JsonValue } from '@backstage/types'; export interface Config { /** * LdapOrgEntityProvider / LdapOrgReaderProcessor configuration + * + * @deprecated This exists for backwards compatibility only and will be removed in the future. */ ldap?: { /** @@ -240,12 +242,237 @@ export interface Config { /** * Configuration options for the catalog plugin. - * - * TODO(freben): Deprecate this entire block */ catalog?: { + /** + * List of provider-specific options and attributes + */ + providers?: { + /** + * LdapOrg provider key + */ + ldapOrg: { + /** + * Id of the LdapOrg provider + */ + [id: string]: { + /** + * The prefix of the target that this matches on, e.g. + * "ldaps://ds.example.net", with no trailing slash. + */ + target: string; + + /** + * The settings to use for the bind command. If none are specified, + * the bind command is not issued. + */ + bind?: { + /** + * The DN of the user to auth as. + * + * E.g. "uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The secret of the user to auth as (its password). + * + * @visibility secret + */ + secret: string; + }; + + /** + * TLS settings + */ + tls?: { + // Node TLS rejectUnauthorized + rejectUnauthorized?: boolean; + }; + + /** + * The settings that govern the reading and interpretation of users. + */ + users: { + /** + * The DN under which users are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "uid". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "uid". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. Defaults to + * "mail". + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.memberOf field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + }; + }; + + /** + * The settings that govern the reading and interpretation of groups. + */ + groups: { + /** + * The DN under which groups are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "cn". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "cn". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. Defaults to + * "description". + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.type field of the entity. Defaults to "groupType". + */ + type?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.parent field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.children field of the entity. Defaults to "member". + */ + members?: string; + }; + }; + }; + }; + }; /** * List of processor-specific options and attributes + * + * @deprecated This exists for backwards compatibility only and will be removed in the future. */ processors?: { /** diff --git a/plugins/catalog-backend-module-ldap/src/index.ts b/plugins/catalog-backend-module-ldap/src/index.ts index 243044369c..f3ffacb93a 100644 --- a/plugins/catalog-backend-module-ldap/src/index.ts +++ b/plugins/catalog-backend-module-ldap/src/index.ts @@ -22,3 +22,8 @@ export * from './processors'; export * from './ldap'; +export { + catalogModuleLdapOrgEntityProvider as default, + ldapOrgEntityProviderTransformsExtensionPoint, + type LdapOrgEntityProviderTransformsExtensionPoint, +} from './module'; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts index 722625f3ed..ddea9de03f 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts @@ -15,26 +15,31 @@ */ import { ConfigReader } from '@backstage/config'; -import { readLdapConfig } from './config'; +import { readProviderConfigs } from './config'; describe('readLdapConfig', () => { it('applies all of the defaults', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - }, - groups: { - dn: 'gdn', + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + }, + groups: { + dn: 'gdn', + }, + }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); const expected = [ { + id: 'default', target: 'target', bind: undefined, users: { @@ -76,72 +81,77 @@ describe('readLdapConfig', () => { it('reads all the values', () => { const config = { - providers: [ - { - target: 'target', - bind: { dn: 'bdn', secret: 's' }, - tls: { - rejectUnauthorized: false, - keys: '/tmp/keys.pem', - certs: '/tmp/certs.pem', - }, - users: { - dn: 'udn', - options: { - scope: 'base', - attributes: ['*'], - filter: 'f', - paged: true, - timeLimit: 42, - sizeLimit: 100, - derefAliases: 0, - typesOnly: false, - }, - set: { p: 'v' }, - map: { - rdn: 'u', - name: 'v', - description: 'd', - displayName: 'c', - email: 'm', - picture: 'p', - memberOf: 'm', - }, - }, - groups: { - dn: 'gdn', - options: { - scope: 'base', - attributes: ['*'], - filter: 'f', - paged: { - pageSize: 7, - pagePause: true, + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + bind: { dn: 'bdn', secret: 's' }, + tls: { + rejectUnauthorized: false, + keys: '/tmp/keys.pem', + certs: '/tmp/certs.pem', + }, + users: { + dn: 'udn', + options: { + scope: 'base', + attributes: ['*'], + filter: 'f', + paged: true, + timeLimit: 42, + sizeLimit: 100, + derefAliases: 0, + typesOnly: false, + }, + set: { p: 'v' }, + map: { + rdn: 'u', + name: 'v', + description: 'd', + displayName: 'c', + email: 'm', + picture: 'p', + memberOf: 'm', + }, + }, + groups: { + dn: 'gdn', + options: { + scope: 'base', + attributes: ['*'], + filter: 'f', + paged: { + pageSize: 7, + pagePause: true, + }, + timeLimit: 42, + sizeLimit: 100, + derefAliases: 1, + typesOnly: true, + }, + set: { p: 'v' }, + map: { + rdn: 'u', + name: 'v', + description: 'd', + type: 't', + displayName: 'c', + email: 'm', + picture: 'p', + memberOf: 'm', + members: 'n', + }, }, - timeLimit: 42, - sizeLimit: 100, - derefAliases: 1, - typesOnly: true, - }, - set: { p: 'v' }, - map: { - rdn: 'u', - name: 'v', - description: 'd', - type: 't', - displayName: 'c', - email: 'm', - picture: 'p', - memberOf: 'm', - members: 'n', }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); const expected = [ { + id: 'default', target: 'target', bind: { dn: 'bdn', secret: 's' }, tls: { @@ -207,30 +217,34 @@ describe('readLdapConfig', () => { it('supports multiline ldap query filter', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - options: { - filter: ` - (| - (cn=foo bar) - (cn=bar) - ) - `, - }, - }, - groups: { - dn: 'gdn', - options: { - filter: 'f', + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + options: { + filter: ` + (| + (cn=foo bar) + (cn=bar) + ) + `, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + }, }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); const expected = '(|(cn=foo bar)(cn=bar))'; expect(actual[0].users.options.filter).toEqual(expected); @@ -238,64 +252,72 @@ describe('readLdapConfig', () => { it('supports a dot nested set structure', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - options: { - filter: 'f', - }, - set: { - 'metadata.annotations': { - a: 'b', + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + 'metadata.annotations': { + a: 'b', + }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, }, }, }, - groups: { - dn: 'gdn', - options: { - filter: 'f', - }, - set: { - x: { a: 'b' }, - }, - }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); expect(actual[0].users.set).toEqual({ 'metadata.annotations': { a: 'b' } }); }); it('throws on attempts to modify the set structure', () => { const config = { - providers: [ - { - target: 'target', - users: { - dn: 'udn', - options: { - filter: 'f', - }, - set: { - x: { a: 'b' }, - }, - }, - groups: { - dn: 'gdn', - options: { - filter: 'f', - }, - set: { - x: { a: 'b' }, + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, }, }, }, - ], + }, }; - const actual = readLdapConfig(new ConfigReader(config)); + const actual = readProviderConfigs(new ConfigReader(config)); expect(() => { (actual[0].users.set as any).y = 2; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 08aee5feee..2bd21fd166 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { JsonValue } from '@backstage/types'; import { SearchOptions } from 'ldapjs'; @@ -27,6 +31,8 @@ import { RecursivePartial } from './util'; * @public */ export type LdapProviderConfig = { + // The id of the + id: string; // The prefix of the target that this matches on, e.g. // "ldaps://ds.example.net", with no trailing slash. target: string; @@ -39,6 +45,8 @@ export type LdapProviderConfig = { users: UserConfig; // The settings that govern the reading and interpretation of groups groups: GroupConfig; + // Schedule configuration for refresh tasks. + schedule?: TaskScheduleDefinition; }; /** @@ -184,159 +192,160 @@ const defaultConfig = { }, }; +function freeze(data: T): T { + return JSON.parse(JSON.stringify(data), (_key, value) => { + if (typeof value === 'object' && value !== null) { + Object.freeze(value); + } + return value; + }); +} + +function readTlsConfig( + c: Config | undefined, +): LdapProviderConfig['tls'] | undefined { + if (!c) { + return undefined; + } + return { + rejectUnauthorized: c.getOptionalBoolean('rejectUnauthorized'), + keys: c.getOptionalString('keys'), + certs: c.getOptionalString('certs'), + }; +} + +function readBindConfig( + c: Config | undefined, +): LdapProviderConfig['bind'] | undefined { + if (!c) { + return undefined; + } + return { + dn: c.getString('dn'), + secret: c.getString('secret'), + }; +} + +function readOptionsConfig(c: Config | undefined): SearchOptions { + if (!c) { + return {}; + } + + const paged = readOptionsPagedConfig(c); + + return { + scope: c.getOptionalString('scope') as SearchOptions['scope'], + filter: formatFilter(c.getOptionalString('filter')), + attributes: c.getOptionalStringArray('attributes'), + sizeLimit: c.getOptionalNumber('sizeLimit'), + timeLimit: c.getOptionalNumber('timeLimit'), + derefAliases: c.getOptionalNumber('derefAliases'), + typesOnly: c.getOptionalBoolean('typesOnly'), + ...(paged !== undefined ? { paged } : undefined), + }; +} + +function readOptionsPagedConfig(c: Config): SearchOptions['paged'] { + const pagedConfig = c.getOptional('paged'); + if (pagedConfig === undefined) { + return undefined; + } + + if (pagedConfig === true || pagedConfig === false) { + return pagedConfig; + } + + const pageSize = c.getOptionalNumber('paged.pageSize'); + const pagePause = c.getOptionalBoolean('paged.pagePause'); + return { + ...(pageSize !== undefined ? { pageSize } : undefined), + ...(pagePause !== undefined ? { pagePause } : undefined), + }; +} + +function readSetConfig( + c: Config | undefined, +): { [path: string]: JsonValue } | undefined { + if (!c) { + return undefined; + } + return c.get(); +} + +function readUserMapConfig( + c: Config | undefined, +): Partial { + if (!c) { + return {}; + } + + return { + rdn: c.getOptionalString('rdn'), + name: c.getOptionalString('name'), + description: c.getOptionalString('description'), + displayName: c.getOptionalString('displayName'), + email: c.getOptionalString('email'), + picture: c.getOptionalString('picture'), + memberOf: c.getOptionalString('memberOf'), + }; +} + +function readGroupMapConfig( + c: Config | undefined, +): Partial { + if (!c) { + return {}; + } + + return { + rdn: c.getOptionalString('rdn'), + name: c.getOptionalString('name'), + description: c.getOptionalString('description'), + type: c.getOptionalString('type'), + displayName: c.getOptionalString('displayName'), + email: c.getOptionalString('email'), + picture: c.getOptionalString('picture'), + memberOf: c.getOptionalString('memberOf'), + members: c.getOptionalString('members'), + }; +} + +function readUserConfig( + c: Config, +): RecursivePartial { + return { + dn: c.getString('dn'), + options: readOptionsConfig(c.getOptionalConfig('options')), + set: readSetConfig(c.getOptionalConfig('set')), + map: readUserMapConfig(c.getOptionalConfig('map')), + }; +} + +function readGroupConfig( + c: Config, +): RecursivePartial { + return { + dn: c.getString('dn'), + options: readOptionsConfig(c.getOptionalConfig('options')), + set: readSetConfig(c.getOptionalConfig('set')), + map: readGroupMapConfig(c.getOptionalConfig('map')), + }; +} + +function formatFilter(filter?: string): string | undefined { + // Remove extra whitespace between blocks to support multiline filters from the configuration + return filter?.replace(/\s*(\(|\))/g, '$1')?.trim(); +} + /** * Parses configuration. * * @param config - The root of the LDAP config hierarchy * * @public + * @deprecated This exists for backwards compatibility only and will be removed in the future. */ -export function readLdapConfig(config: Config): LdapProviderConfig[] { - function freeze(data: T): T { - return JSON.parse(JSON.stringify(data), (_key, value) => { - if (typeof value === 'object' && value !== null) { - Object.freeze(value); - } - return value; - }); - } - - function readTlsConfig( - c: Config | undefined, - ): LdapProviderConfig['tls'] | undefined { - if (!c) { - return undefined; - } - return { - rejectUnauthorized: c.getOptionalBoolean('rejectUnauthorized'), - keys: c.getOptionalString('keys'), - certs: c.getOptionalString('certs'), - }; - } - - function readBindConfig( - c: Config | undefined, - ): LdapProviderConfig['bind'] | undefined { - if (!c) { - return undefined; - } - return { - dn: c.getString('dn'), - secret: c.getString('secret'), - }; - } - - function readOptionsConfig(c: Config | undefined): SearchOptions { - if (!c) { - return {}; - } - - const paged = readOptionsPagedConfig(c); - - return { - scope: c.getOptionalString('scope') as SearchOptions['scope'], - filter: formatFilter(c.getOptionalString('filter')), - attributes: c.getOptionalStringArray('attributes'), - sizeLimit: c.getOptionalNumber('sizeLimit'), - timeLimit: c.getOptionalNumber('timeLimit'), - derefAliases: c.getOptionalNumber('derefAliases'), - typesOnly: c.getOptionalBoolean('typesOnly'), - ...(paged !== undefined ? { paged } : undefined), - }; - } - - function readOptionsPagedConfig(c: Config): SearchOptions['paged'] { - const pagedConfig = c.getOptional('paged'); - if (pagedConfig === undefined) { - return undefined; - } - - if (pagedConfig === true || pagedConfig === false) { - return pagedConfig; - } - - const pageSize = c.getOptionalNumber('paged.pageSize'); - const pagePause = c.getOptionalBoolean('paged.pagePause'); - return { - ...(pageSize !== undefined ? { pageSize } : undefined), - ...(pagePause !== undefined ? { pagePause } : undefined), - }; - } - - function readSetConfig( - c: Config | undefined, - ): { [path: string]: JsonValue } | undefined { - if (!c) { - return undefined; - } - return c.get(); - } - - function readUserMapConfig( - c: Config | undefined, - ): Partial { - if (!c) { - return {}; - } - - return { - rdn: c.getOptionalString('rdn'), - name: c.getOptionalString('name'), - description: c.getOptionalString('description'), - displayName: c.getOptionalString('displayName'), - email: c.getOptionalString('email'), - picture: c.getOptionalString('picture'), - memberOf: c.getOptionalString('memberOf'), - }; - } - - function readGroupMapConfig( - c: Config | undefined, - ): Partial { - if (!c) { - return {}; - } - - return { - rdn: c.getOptionalString('rdn'), - name: c.getOptionalString('name'), - description: c.getOptionalString('description'), - type: c.getOptionalString('type'), - displayName: c.getOptionalString('displayName'), - email: c.getOptionalString('email'), - picture: c.getOptionalString('picture'), - memberOf: c.getOptionalString('memberOf'), - members: c.getOptionalString('members'), - }; - } - - function readUserConfig( - c: Config, - ): RecursivePartial { - return { - dn: c.getString('dn'), - options: readOptionsConfig(c.getOptionalConfig('options')), - set: readSetConfig(c.getOptionalConfig('set')), - map: readUserMapConfig(c.getOptionalConfig('map')), - }; - } - - function readGroupConfig( - c: Config, - ): RecursivePartial { - return { - dn: c.getString('dn'), - options: readOptionsConfig(c.getOptionalConfig('options')), - set: readSetConfig(c.getOptionalConfig('set')), - map: readGroupMapConfig(c.getOptionalConfig('map')), - }; - } - - function formatFilter(filter?: string): string | undefined { - // Remove extra whitespace between blocks to support multiline filters from the configuration - return filter?.replace(/\s*(\(|\))/g, '$1')?.trim(); - } - +export function readLdapLegacyConfig(config: Config): LdapProviderConfig[] { const providerConfigs = config.getOptionalConfigArray('providers') ?? []; return providerConfigs.map(c => { const newConfig = { @@ -353,3 +362,40 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { return freeze(merged) as LdapProviderConfig; }); } + +/** + * Parses all configured providers. + * + * @param config - The root of the LDAP config hierarchy + * + * @public + */ +export function readProviderConfigs(config: Config): LdapProviderConfig[] { + const providersConfig = config.getOptionalConfig('catalog.providers.ldapOrg'); + if (!providersConfig) { + return []; + } + + return providersConfig.keys().map(id => { + const c = providersConfig.getConfig(id); + + const schedule = c.has('schedule') + ? readTaskScheduleDefinitionFromConfig(c.getConfig('schedule')) + : undefined; + + const newConfig = { + id, + target: trimEnd(c.getString('target'), '/'), + tls: readTlsConfig(c.getOptionalConfig('tls')), + bind: readBindConfig(c.getOptionalConfig('bind')), + users: readUserConfig(c.getConfig('users')), + groups: readGroupConfig(c.getConfig('groups')), + schedule, + }; + const merged = mergeWith({}, defaultConfig, newConfig, (_into, from) => { + // Replace arrays instead of merging, otherwise default behavior + return Array.isArray(from) ? from : undefined; + }); + return freeze(merged) as LdapProviderConfig; + }); +} diff --git a/plugins/catalog-backend-module-ldap/src/ldap/index.ts b/plugins/catalog-backend-module-ldap/src/ldap/index.ts index 6d7800fbfd..cfab5a4444 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/index.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/index.ts @@ -16,7 +16,7 @@ export { LdapClient } from './client'; export { mapStringAttr } from './util'; -export { readLdapConfig } from './config'; +export { readProviderConfigs, readLdapLegacyConfig } from './config'; export type { LdapProviderConfig, GroupConfig, diff --git a/plugins/catalog-backend-module-ldap/src/module.ts b/plugins/catalog-backend-module-ldap/src/module.ts new file mode 100644 index 0000000000..ad76b133f1 --- /dev/null +++ b/plugins/catalog-backend-module-ldap/src/module.ts @@ -0,0 +1,114 @@ +/* + * 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 { + coreServices, + createBackendModule, + createExtensionPoint, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { + GroupTransformer, + UserTransformer, +} from '@backstage/plugin-catalog-backend-module-ldap'; +import { LdapOrgEntityProvider } from './processors'; + +/** + * Interface for {@link LdapOrgEntityProviderTransformsExtensionPoint}. + * + * @public + */ +export interface LdapOrgEntityProviderTransformsExtensionPoint { + /** + * Set the function that transforms a user entry in LDAP to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + setUserTransformer( + transformer: UserTransformer | Record, + ): void; + + /** + * Set the function that transforms a group entry in LDAP to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + setGroupTransformer( + transformer: GroupTransformer | Record, + ): void; +} + +/** + * Extension point used to customize the transforms used by the module. + * + * @public + */ +export const ldapOrgEntityProviderTransformsExtensionPoint = + createExtensionPoint({ + id: 'catalog.ldapOrgEntityProvider.transforms', + }); + +/** + * Registers the LdapOrgEntityProvider with the catalog processing extension point. + * + * @public + */ +export const catalogModuleLdapOrgEntityProvider = createBackendModule({ + pluginId: 'catalog', + moduleId: 'ldapOrgEntityProvider', + register(env) { + let userTransformer: + | UserTransformer + | Record + | undefined; + let groupTransformer: + | GroupTransformer + | Record + | undefined; + + env.registerExtensionPoint(ldapOrgEntityProviderTransformsExtensionPoint, { + setUserTransformer(transformer) { + if (userTransformer) { + throw new Error('User transformer may only be set once'); + } + userTransformer = transformer; + }, + setGroupTransformer(transformer) { + if (groupTransformer) { + throw new Error('Group transformer may only be set once'); + } + groupTransformer = transformer; + }, + }); + + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: coreServices.rootConfig, + logger: coreServices.logger, + scheduler: coreServices.scheduler, + }, + async init({ catalog, config, logger, scheduler }) { + catalog.addEntityProvider( + LdapOrgEntityProvider.fromConfig(config, { + logger, + scheduler, + userTransformer: userTransformer, + groupTransformer: groupTransformer, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts index 97ea4a53b0..fb8f27d685 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -32,18 +32,65 @@ import { LdapClient, LdapProviderConfig, LDAP_DN_ANNOTATION, - readLdapConfig, readLdapOrg, UserTransformer, } from '../ldap'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { readLdapLegacyConfig, readProviderConfigs } from '../ldap'; /** * Options for {@link LdapOrgEntityProvider}. * * @public */ -export interface LdapOrgEntityProviderOptions { +export type LdapOrgEntityProviderOptions = + | LdapOrgEntityProviderLegacyOptions + | { + /** + * The logger to use. + */ + logger: LoggerService; + + /** + * The refresh schedule to use. + * + * @remarks + * + * If you pass in 'manual', you are responsible for calling the `read` method + * manually at some interval. + * + * But more commonly you will pass in the result of + * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * to enable automatic scheduling of tasks. + */ + schedule?: 'manual' | TaskRunner; + + /** + * Scheduler used to schedule refreshes based on + * the schedule config. + */ + scheduler?: PluginTaskScheduler; + + /** + * The function that transforms a user entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + userTransformer?: UserTransformer | Record; + + /** + * The function that transforms a group entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + groupTransformer?: GroupTransformer | Record; + }; + +/** + * Options for {@link LdapOrgEntityProvider}. + * + * @public + * @deprecated This interface exists for backwards compatibility only and will be removed in the future. + */ +export interface LdapOrgEntityProviderLegacyOptions { /** * A unique, stable identifier for this provider. * @@ -109,12 +156,68 @@ export class LdapOrgEntityProvider implements EntityProvider { static fromConfig( configRoot: Config, options: LdapOrgEntityProviderOptions, + ): LdapOrgEntityProvider[] { + if ('id' in options) { + return [LdapOrgEntityProvider.fromLegacyConfig(configRoot, options)]; + } + + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + + function getTransformer( + id: string, + transformers?: T | Record, + ): T | undefined { + if (['undefined', 'function'].includes(typeof transformers)) { + return transformers as T; + } + + return (transformers as Record)[id]; + } + + return readProviderConfigs(configRoot).map(providerConfig => { + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for LdapOrgEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + const provider = new LdapOrgEntityProvider({ + id: providerConfig.id, + provider: providerConfig, + logger: options.logger, + userTransformer: getTransformer( + providerConfig.id, + options.userTransformer, + ), + groupTransformer: getTransformer( + providerConfig.id, + options.groupTransformer, + ), + }); + + if (taskRunner !== 'manual') { + provider.schedule(taskRunner); + } + + return provider; + }); + } + + static fromLegacyConfig( + configRoot: Config, + options: LdapOrgEntityProviderLegacyOptions, ): LdapOrgEntityProvider { // TODO(freben): Deprecate the old catalog.processors.ldapOrg config const config = configRoot.getOptionalConfig('ldap') || configRoot.getOptionalConfig('catalog.processors.ldapOrg'); - const providers = config ? readLdapConfig(config) : []; + const providers = config ? readLdapLegacyConfig(config) : []; const provider = providers.find(p => options.target === p.target); if (!provider) { throw new TypeError( @@ -134,7 +237,9 @@ export class LdapOrgEntityProvider implements EntityProvider { logger, }); - result.schedule(options.schedule); + if (options.schedule !== 'manual') { + result.schedule(options.schedule); + } return result; } @@ -206,14 +311,10 @@ export class LdapOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule(schedule: LdapOrgEntityProviderOptions['schedule']) { - if (schedule === 'manual') { - return; - } - + private schedule(taskRunner: TaskRunner) { this.scheduleFn = async () => { const id = `${this.getProviderName()}:refresh`; - await schedule.run({ + await taskRunner.run({ id, fn: async () => { const logger = this.options.logger.child({ diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index c920968fc1..acbe128e0b 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -19,7 +19,7 @@ import { GroupTransformer, LdapClient, LdapProviderConfig, - readLdapConfig, + readLdapLegacyConfig, readLdapOrg, UserTransformer, } from '../ldap'; @@ -56,7 +56,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { configRoot.getOptionalConfig('catalog.processors.ldapOrg'); return new LdapOrgReaderProcessor({ ...options, - providers: config ? readLdapConfig(config) : [], + providers: config ? readLdapLegacyConfig(config) : [], }); } diff --git a/plugins/catalog-backend-module-ldap/src/processors/index.ts b/plugins/catalog-backend-module-ldap/src/processors/index.ts index 5ed0095c3e..1a0a6a69c8 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/index.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/index.ts @@ -15,5 +15,8 @@ */ export { LdapOrgEntityProvider } from './LdapOrgEntityProvider'; -export type { LdapOrgEntityProviderOptions } from './LdapOrgEntityProvider'; +export type { + LdapOrgEntityProviderOptions, + LdapOrgEntityProviderLegacyOptions, +} from './LdapOrgEntityProvider'; export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor'; From 31ecd983a0b15c9fdeb3090174308ee51de58f71 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:03:11 +0200 Subject: [PATCH 536/567] feat: add logs and finally block Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabPipelineTrigger.ts | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts index 2edb529ea7..6937fe44aa 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -55,36 +55,43 @@ export const createTriggerGitlabPipelineAction = (options: { output: pipelineOutputProperties, }, async handler(ctx) { + let pipelineTokenResponse: PipelineTriggerTokenSchema | null = null; + + const { repoUrl, projectId, tokenDescription, token, branch } = + commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + + const { host } = parseRepoUrl(repoUrl, integrations); + const api = getClient({ host, integrations, token }); + try { - const { repoUrl, projectId, tokenDescription, token, branch } = - commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); + // Create a pipeline token + pipelineTokenResponse = (await api.PipelineTriggerTokens.create( + projectId, + tokenDescription, + )) as PipelineTriggerTokenSchema; - const { host } = parseRepoUrl(repoUrl, integrations); - const api = getClient({ host, integrations, token }); - - // Get a pipeline token - const createdPipelineTokenResponse = - (await api.PipelineTriggerTokens.create( - projectId, - tokenDescription, - )) as PipelineTriggerTokenSchema; - - if (!createdPipelineTokenResponse.token) { + if (!pipelineTokenResponse.token) { + ctx.logger.error('Failed to create pipeline token.'); return; } + ctx.logger.info( + `Pipeline token id ${pipelineTokenResponse.id} created.`, + ); + // Use the pipeline token to trigger the pipeline in the project const pipelineTriggerResponse = (await api.PipelineTriggerTokens.trigger( projectId, branch, - createdPipelineTokenResponse.token, + pipelineTokenResponse.token, )) as ExpandedPipelineSchema; - // Delete the pipeline token - await api.PipelineTriggerTokens.remove( - projectId, - createdPipelineTokenResponse.id, - ); + if (!pipelineTriggerResponse.id) { + ctx.logger.error('Failed to trigger pipeline.'); + return; + } + + ctx.logger.info(`Pipeline id ${pipelineTriggerResponse.id} triggered.`); ctx.output('pipelineUrl', pipelineTriggerResponse.web_url); } catch (error: any) { @@ -96,6 +103,23 @@ export const createTriggerGitlabPipelineAction = (options: { } // Handling other errors throw new InputError(`Failed to trigger Pipeline: ${error.message}`); + } finally { + // Delete the pipeline token if it was created + if (pipelineTokenResponse && pipelineTokenResponse.id) { + try { + await api.PipelineTriggerTokens.remove( + projectId, + pipelineTokenResponse.id, + ); + ctx.logger.info( + `Deleted pipeline token ${pipelineTokenResponse.id}.`, + ); + } catch (error: any) { + ctx.logger.error( + `Failed to delete pipeline token id ${pipelineTokenResponse.id}.`, + ); + } + } } }, }); From c22bc6d5a23c48b158bc114db6a2505bdc1622f2 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:04:01 +0200 Subject: [PATCH 537/567] feat: add tests Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabPipelineTrigger.test.ts | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts new file mode 100644 index 0000000000..a7412032a4 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts @@ -0,0 +1,235 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/core-app-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; + +const mockGitlabClient = { + PipelineTriggerTokens: { + create: jest.fn(), + trigger: jest.fn(), + remove: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:pipeline:trigger', () => { + beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + jest.useFakeTimers({ + now: new Date(1988, 5, 3, 12, 0, 0), + }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createTriggerGitlabPipelineAction({ integrations }); + + it('should return a Pipeline Token Id', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'My cool pipeline token', + createdAt: new Date().toISOString(), + last_used: null, + token: 'glptt-abcdef', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockResolvedValue({ + id: 99, + web_url: 'https://gitlab.com/hangar18-/pipelines/99', + }); + + await action.handler({ + ...mockContext, + }); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + 123, + 'My cool pipeline token', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + 123, + 'main', + 'glptt-abcdef', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + 123, + 42, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'pipelineUrl', + 'https://gitlab.com/hangar18-/pipelines/99', + ); + }); + + it('should throw error if pipeline token cannot be created', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockRejectedValue( + new Error('Failed to create token'), + ); + + await expect( + action.handler({ + ...mockContext, + }), + ).rejects.toThrow('Failed to create token'); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + 123, + 'My cool pipeline token', + ); + + expect( + mockGitlabClient.PipelineTriggerTokens.trigger, + ).not.toHaveBeenCalled(); + + expect( + mockGitlabClient.PipelineTriggerTokens.remove, + ).not.toHaveBeenCalled(); + }); + + it('throw error if pipeline cannot be triggered', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'My cool pipeline token', + createdAt: new Date().toISOString(), + last_used: null, + token: 'glptt-abcdef', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockRejectedValue( + new Error('Failed to trigger pipeline'), + ); + + await expect( + action.handler({ + ...mockContext, + }), + ).rejects.toThrow('Failed to trigger pipeline'); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + 123, + 'My cool pipeline token', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + 123, + 'main', + 'glptt-abcdef', + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + 123, + 42, + ); + }); + it('should clean up pipeline token on failure', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'My cool pipeline token', + createdAt: new Date().toISOString(), + last_used: null, + token: 'glptt-abcdef', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockRejectedValue( + new Error('Failed to trigger pipeline'), + ); + + await expect( + action.handler({ + ...mockContext, + }), + ).rejects.toThrow('Failed to trigger pipeline'); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + 123, + 42, + ); + }); +}); From 5c5622290e4285ef5b8581734f6132ddc4d1f849 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:06:05 +0200 Subject: [PATCH 538/567] chore: rename files in the gitlab module Signed-off-by: ElaineDeMattosSilvaB --- ...t.ts => gitlabGroupEnsureExists.examples.test.ts} | 6 +++--- ...amples.ts => gitlabGroupEnsureExists.examples.ts} | 0 ...ction.test.ts => gitlabGroupEnsureExists.test.ts} | 4 ++-- ...ureExistsAction.ts => gitlabGroupEnsureExists.ts} | 8 ++++---- ...ion.examples.ts => gitlabIssueCreate.examples.ts} | 0 ...IssueAction.test.ts => gitlabIssueCreate.test.ts} | 4 ++-- ...eateGitlabIssueAction.ts => gitlabIssueCreate.ts} | 2 +- ... gitlabProjectAccessTokenCreate.examples.test.ts} | 6 +++--- ...ts => gitlabProjectAccessTokenCreate.examples.ts} | 0 ...enAction.ts => gitlabProjectAccessTokenCreate.ts} | 2 +- ... gitlabProjectDeployTokenCreate.examples.test.ts} | 8 ++++---- ...ts => gitlabProjectDeployTokenCreate.examples.ts} | 0 ...est.ts => gitlabProjectDeployTokenCreate.test.ts} | 6 +++--- ...enAction.ts => gitlabProjectDeployTokenCreate.ts} | 10 +++++----- ... => gitlabProjectVariableCreate.examples.test.ts} | 8 ++++---- ...es.ts => gitlabProjectVariableCreate.examples.ts} | 0 ...iableAction.ts => gitlabProjectVariableCreate.ts} | 8 ++++---- .../src/actions/index.ts | 12 ++++++------ 18 files changed, 42 insertions(+), 42 deletions(-) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.examples.test.ts => gitlabGroupEnsureExists.examples.test.ts} (96%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.examples.ts => gitlabGroupEnsureExists.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.test.ts => gitlabGroupEnsureExists.test.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabGroupEnsureExistsAction.ts => gitlabGroupEnsureExists.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabIssueAction.examples.ts => gitlabIssueCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabIssueAction.test.ts => gitlabIssueCreate.test.ts} (98%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabIssueAction.ts => gitlabIssueCreate.ts} (99%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectAccessTokenAction.examples.test.ts => gitlabProjectAccessTokenCreate.examples.test.ts} (95%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectAccessTokenAction.examples.ts => gitlabProjectAccessTokenCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectAccessTokenAction.ts => gitlabProjectAccessTokenCreate.ts} (98%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.examples.test.ts => gitlabProjectDeployTokenCreate.examples.test.ts} (96%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.examples.ts => gitlabProjectDeployTokenCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.test.ts => gitlabProjectDeployTokenCreate.test.ts} (96%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectDeployTokenAction.ts => gitlabProjectDeployTokenCreate.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectVariableAction.examples.test.ts => gitlabProjectVariableCreate.examples.test.ts} (97%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectVariableAction.examples.ts => gitlabProjectVariableCreate.examples.ts} (100%) rename plugins/scaffolder-backend-module-gitlab/src/actions/{createGitlabProjectVariableAction.ts => gitlabProjectVariableCreate.ts} (97%) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts similarity index 96% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index 2d077c7400..b9377fd97b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { createGitlabGroupEnsureExistsAction } from './createGitlabGroupEnsureExistsAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; -import { examples } from './createGitlabGroupEnsureExistsAction.examples'; +import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; +import { examples } from './gitlabGroupEnsureExists.examples'; const mockGitlabClient = { Groups: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts index 4dc0305c2e..a92637ba46 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { createGitlabGroupEnsureExistsAction } from './createGitlabGroupEnsureExistsAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; const mockGitlabClient = { Groups: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts index 2cf583a95d..29f260fb75 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabGroupEnsureExistsAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { Gitlab } from '@gitbeaker/node'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { GroupSchema } from '@gitbeaker/core/dist/types/resources/Groups'; +import { Gitlab } from '@gitbeaker/node'; +import { z } from 'zod'; import commonGitlabConfig from '../commonGitlabConfig'; import { getToken } from '../util'; -import { z } from 'zod'; -import { examples } from './createGitlabGroupEnsureExistsAction.examples'; +import { examples } from './gitlabGroupEnsureExists.examples'; /** * Creates an `gitlab:group:ensureExists` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts similarity index 98% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts index feea436a1e..af359c39b5 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { createGitlabIssueAction, IssueType } from './createGitlabIssueAction'; import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabIssueAction, IssueType } from './gitlabIssueCreate'; const mockGitlabClient = { Issues: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts similarity index 99% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts index 894caf635c..97ac3cd6fb 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.ts @@ -18,7 +18,7 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import commonGitlabConfig from '../commonGitlabConfig'; -import { examples } from './createGitlabIssueAction.examples'; +import { examples } from './gitlabIssueCreate.examples'; import { z } from 'zod'; import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; import { Gitlab, CreateIssueOptions, IssueSchema } from '@gitbeaker/rest'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts similarity index 95% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts index 5afeaba8ae..811926cc82 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts @@ -15,10 +15,10 @@ */ import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import yaml from 'yaml'; -import { createGitlabProjectAccessTokenAction } from './createGitlabProjectAccessTokenAction'; // Adjust the import based on your project structure -import { examples } from './createGitlabProjectAccessTokenAction.examples'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import yaml from 'yaml'; +import { createGitlabProjectAccessTokenAction } from './gitlabProjectAccessTokenCreate'; // Adjust the import based on your project structure +import { examples } from './gitlabProjectAccessTokenCreate.examples'; import { DateTime } from 'luxon'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts similarity index 98% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts index 2193dbb02f..5d31686a6e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectAccessTokenAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.ts @@ -21,7 +21,7 @@ import { AccessTokenScopes, Gitlab } from '@gitbeaker/rest'; import { DateTime } from 'luxon'; import { z } from 'zod'; import { getToken } from '../util'; -import { examples } from './createGitlabProjectAccessTokenAction.examples'; +import { examples } from './gitlabProjectAccessTokenCreate.examples'; /** * Creates a `gitlab:projectAccessToken:create` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.test.ts similarity index 96% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.test.ts index dd3b694670..d56d6de556 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { createGitlabProjectDeployTokenAction } from './createGitlabProjectDeployTokenAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; -import { examples } from './createGitlabProjectDeployTokenAction.examples'; +import { createGitlabProjectDeployTokenAction } from './gitlabProjectDeployTokenCreate'; +import { examples } from './gitlabProjectDeployTokenCreate.examples'; const mockGitlabClient = { ProjectDeployTokens: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.test.ts similarity index 96% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.test.ts index cab72c9a6d..146692006b 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { createGitlabProjectDeployTokenAction } from './createGitlabProjectDeployTokenAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabProjectDeployTokenAction } from './gitlabProjectDeployTokenCreate'; const mockGitlabClient = { ProjectDeployTokens: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts index 87d4314b6f..79ecc0de4e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectDeployTokenCreate.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { Gitlab } from '@gitbeaker/node'; +import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { DeployTokenScope } from '@gitbeaker/core/dist/types/templates/ResourceDeployTokens'; +import { Gitlab } from '@gitbeaker/node'; +import { z } from 'zod'; import commonGitlabConfig from '../commonGitlabConfig'; import { getToken } from '../util'; -import { InputError } from '@backstage/errors'; -import { z } from 'zod'; -import { examples } from './createGitlabProjectDeployTokenAction.examples'; +import { examples } from './gitlabProjectDeployTokenCreate.examples'; /** * Creates a `gitlab:projectDeployToken:create` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts index d1c37ccd9d..1eeb01ca91 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { createGitlabProjectVariableAction } from './createGitlabProjectVariableAction'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import yaml from 'yaml'; -import { examples } from './createGitlabProjectVariableAction.examples'; +import { createGitlabProjectVariableAction } from './gitlabProjectVariableCreate'; +import { examples } from './gitlabProjectVariableCreate.examples'; const mockGitlabClient = { ProjectVariables: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.ts diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts similarity index 97% rename from plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts index e09a074701..a07ec747fa 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { Gitlab } from '@gitbeaker/node'; -import { getToken } from '../util'; -import commonGitlabConfig from '../commonGitlabConfig'; import { z } from 'zod'; -import { examples } from './createGitlabProjectVariableAction.examples'; +import commonGitlabConfig from '../commonGitlabConfig'; +import { getToken } from '../util'; +import { examples } from './gitlabProjectVariableCreate.examples'; /** * Creates a `gitlab:projectVariable:create` Scaffolder action. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts index fb80a43ff4..c5c24772ef 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './createGitlabGroupEnsureExistsAction'; -export * from './createGitlabIssueAction'; -export * from './createGitlabProjectAccessTokenAction'; -export * from './createGitlabProjectDeployTokenAction'; -export * from './createGitlabProjectVariableAction'; export * from './gitlab'; +export * from './gitlabGroupEnsureExists'; +export * from './gitlabIssueCreate'; export * from './gitlabMergeRequest'; -export * from './gitlabRepoPush'; export * from './gitlabPipelineTrigger'; +export * from './gitlabProjectAccessTokenCreate'; +export * from './gitlabProjectDeployTokenCreate'; +export * from './gitlabProjectVariableCreate'; +export * from './gitlabRepoPush'; From 595819b6adb2f12a99c4db3b9ddce964dcae2886 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:58:46 +0200 Subject: [PATCH 539/567] fix: add changes to api report Signed-off-by: ElaineDeMattosSilvaB --- .../api-report.md | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index fdffba1c7e..5b69210050 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -33,11 +33,11 @@ export const createGitlabIssueAction: (options: { projectId: number; labels?: string | undefined; description?: string | undefined; - weight?: number | undefined; token?: string | undefined; + weight?: number | undefined; assignees?: number[] | undefined; - createdAt?: string | undefined; confidential?: boolean | undefined; + createdAt?: string | undefined; milestoneId?: number | undefined; epicId?: number | undefined; dueDate?: string | undefined; @@ -61,8 +61,8 @@ export const createGitlabProjectAccessTokenAction: (options: { projectId: string | number; name?: string | undefined; token?: string | undefined; - scopes?: string[] | undefined; expiresAt?: string | undefined; + scopes?: string[] | undefined; accessLevel?: number | undefined; }, { @@ -78,8 +78,8 @@ export const createGitlabProjectDeployTokenAction: (options: { name: string; repoUrl: string; projectId: string | number; - username?: string | undefined; token?: string | undefined; + username?: string | undefined; scopes?: string[] | undefined; }, { @@ -118,7 +118,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'update' | 'create' | 'delete' | undefined; }, JsonObject >; @@ -149,8 +149,8 @@ export function createPublishGitlabAction(options: { squash_option?: | 'always' | 'never' - | 'default_on' | 'default_off' + | 'default_on' | undefined; topics?: string[] | undefined; visibility?: 'internal' | 'private' | 'public' | undefined; @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'update' | 'create' | 'delete' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -201,6 +201,22 @@ export const createPublishGitlabMergeRequestAction: (options: { JsonObject >; +// @public +export const createTriggerGitlabPipelineAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + branch: string; + projectId: number; + tokenDescription: string; + token?: string | undefined; + }, + { + pipelineUrl: string; + } +>; + // @public const gitlabModule: () => BackendFeature; export default gitlabModule; From 829e0ec80e35ed2204ab41bdd841737c411a4693 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 12:59:12 +0200 Subject: [PATCH 540/567] feat: add changeset Signed-off-by: ElaineDeMattosSilvaB --- .changeset/soft-flies-live.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/soft-flies-live.md diff --git a/.changeset/soft-flies-live.md b/.changeset/soft-flies-live.md new file mode 100644 index 0000000000..2de68f12ee --- /dev/null +++ b/.changeset/soft-flies-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +--- + +Add new Scaffolder action to trigger GitLab pipelines. From 73e7c13a3b1d84ae0758710c09e02df3ff4961b0 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Mon, 27 May 2024 14:01:30 +0200 Subject: [PATCH 541/567] Update tall-lies-fetch.md Signed-off-by: Ben Lambert --- .changeset/tall-lies-fetch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tall-lies-fetch.md b/.changeset/tall-lies-fetch.md index 391aa95aa5..a4484a5687 100644 --- a/.changeset/tall-lies-fetch.md +++ b/.changeset/tall-lies-fetch.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog': patch --- -Variable 'catalogTranslationRef' is exported in translation.ts, but it was forgotten to also add it to the alpha entrypoint, so the code never became "visible" +Export `catalogTranslationRef` under `/alpha` From 11540f4f707c457241a2c5fcd7277333b50fcb98 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Mon, 27 May 2024 14:04:05 +0200 Subject: [PATCH 542/567] Update strong-moose-work.md Signed-off-by: Ben Lambert --- .changeset/strong-moose-work.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/strong-moose-work.md b/.changeset/strong-moose-work.md index 20d35da7f0..443b708e51 100644 --- a/.changeset/strong-moose-work.md +++ b/.changeset/strong-moose-work.md @@ -1,5 +1,5 @@ --- -'@backstage/repo-tools': minor +'@backstage/repo-tools': patch --- -Add --client-additional-properties option to generate command to pass properties to @openapitools/openapi-generator-cli +Add `--client-additional-properties` option to `openapi generate` command From f67ae7c6c2942d607b7b6612dcbb54b258127245 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Mon, 27 May 2024 14:50:55 +0200 Subject: [PATCH 543/567] fix: add api-report.md Signed-off-by: ElaineDeMattosSilvaB --- .../scaffolder-backend-module-gitlab/api-report.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 5b69210050..102b2e47b0 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -33,11 +33,11 @@ export const createGitlabIssueAction: (options: { projectId: number; labels?: string | undefined; description?: string | undefined; - token?: string | undefined; weight?: number | undefined; + token?: string | undefined; assignees?: number[] | undefined; - confidential?: boolean | undefined; createdAt?: string | undefined; + confidential?: boolean | undefined; milestoneId?: number | undefined; epicId?: number | undefined; dueDate?: string | undefined; @@ -61,8 +61,8 @@ export const createGitlabProjectAccessTokenAction: (options: { projectId: string | number; name?: string | undefined; token?: string | undefined; - expiresAt?: string | undefined; scopes?: string[] | undefined; + expiresAt?: string | undefined; accessLevel?: number | undefined; }, { @@ -78,8 +78,8 @@ export const createGitlabProjectDeployTokenAction: (options: { name: string; repoUrl: string; projectId: string | number; - token?: string | undefined; username?: string | undefined; + token?: string | undefined; scopes?: string[] | undefined; }, { @@ -118,7 +118,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'create' | 'delete' | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; }, JsonObject >; @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'create' | 'delete' | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -206,8 +206,8 @@ export const createTriggerGitlabPipelineAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - repoUrl: string; branch: string; + repoUrl: string; projectId: number; tokenDescription: string; token?: string | undefined; From 0665b7ed50d63485e8b654fce2f1415857a234c4 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 27 May 2024 07:48:02 +0200 Subject: [PATCH 544/567] refactor(backend-plugin-api): rename factory configs to options Signed-off-by: Camila Belo --- .changeset/warm-bees-hope.md | 5 ++ packages/backend-plugin-api/api-report.md | 49 +++++++++++-------- .../src/wiring/factories.ts | 30 ++++++------ .../backend-plugin-api/src/wiring/index.ts | 34 +++++++++++-- 4 files changed, 79 insertions(+), 39 deletions(-) create mode 100644 .changeset/warm-bees-hope.md diff --git a/.changeset/warm-bees-hope.md b/.changeset/warm-bees-hope.md new file mode 100644 index 0000000000..78b77cf5fb --- /dev/null +++ b/.changeset/warm-bees-hope.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +We renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` in order to standardize frontend and backend factories signatures. diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 134e4b61da..5e59378760 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -70,13 +70,8 @@ export interface BackendFeature { $$type: '@backstage/BackendFeature'; } -// @public -export interface BackendModuleConfig { - moduleId: string; - pluginId: string; - // (undocumented) - register(reg: BackendModuleRegistrationPoints): void; -} +// @public @deprecated (undocumented) +export type BackendModuleConfig = CreateBackendModuleOptions; // @public export interface BackendModuleRegistrationPoints { @@ -98,12 +93,8 @@ export interface BackendModuleRegistrationPoints { }): void; } -// @public -export interface BackendPluginConfig { - pluginId: string; - // (undocumented) - register(reg: BackendPluginRegistrationPoints): void; -} +// @public @deprecated (undocumented) +export type BackendPluginConfig = CreateBackendPluginOptions; // @public export interface BackendPluginRegistrationPoints { @@ -223,19 +214,39 @@ export namespace coreServices { // @public export function createBackendModule( - config: BackendModuleConfig, + options: CreateBackendModuleOptions, ): () => BackendFeature; +// @public +export interface CreateBackendModuleOptions { + moduleId: string; + pluginId: string; + // (undocumented) + register(reg: BackendModuleRegistrationPoints): void; +} + // @public export function createBackendPlugin( - config: BackendPluginConfig, + options: CreateBackendPluginOptions, ): () => BackendFeature; +// @public +export interface CreateBackendPluginOptions { + pluginId: string; + // (undocumented) + register(reg: BackendPluginRegistrationPoints): void; +} + // @public export function createExtensionPoint( - config: ExtensionPointConfig, + options: CreateExtensionPointOptions, ): ExtensionPoint; +// @public +export interface CreateExtensionPointOptions { + id: string; +} + // @public export function createServiceFactory< TService, @@ -320,10 +331,8 @@ export type ExtensionPoint = { $$type: '@backstage/ExtensionPoint'; }; -// @public -export interface ExtensionPointConfig { - id: string; -} +// @public @deprecated (undocumented) +export type ExtensionPointConfig = CreateExtensionPointOptions; // @public (undocumented) export interface HttpAuthService { diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts index 9fa0f87bab..d0199e775a 100644 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ b/packages/backend-plugin-api/src/wiring/factories.ts @@ -30,7 +30,7 @@ import { * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ -export interface ExtensionPointConfig { +export interface CreateExtensionPointOptions { /** * The ID of this extension point. * @@ -46,15 +46,15 @@ export interface ExtensionPointConfig { * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} */ export function createExtensionPoint( - config: ExtensionPointConfig, + options: CreateExtensionPointOptions, ): ExtensionPoint { return { - id: config.id, + id: options.id, get T(): T { throw new Error(`tried to read ExtensionPoint.T of ${this}`); }, toString() { - return `extensionPoint{${config.id}}`; + return `extensionPoint{${options.id}}`; }, $$type: '@backstage/ExtensionPoint', }; @@ -67,7 +67,7 @@ export function createExtensionPoint( * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ -export interface BackendPluginConfig { +export interface CreateBackendPluginOptions { /** * The ID of this plugin. * @@ -85,7 +85,7 @@ export interface BackendPluginConfig { * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ export function createBackendPlugin( - config: BackendPluginConfig, + options: CreateBackendPluginOptions, ): () => BackendFeature { const factory: BackendFeatureFactory = () => { let registrations: InternalBackendPluginRegistration[]; @@ -102,7 +102,7 @@ export function createBackendPlugin( let init: InternalBackendPluginRegistration['init'] | undefined = undefined; - config.register({ + options.register({ registerExtensionPoint(ext, impl) { if (init) { throw new Error( @@ -124,14 +124,14 @@ export function createBackendPlugin( if (!init) { throw new Error( - `registerInit was not called by register in ${config.pluginId}`, + `registerInit was not called by register in ${options.pluginId}`, ); } registrations = [ { type: 'plugin', - pluginId: config.pluginId, + pluginId: options.pluginId, extensionPoints, init, }, @@ -152,7 +152,7 @@ export function createBackendPlugin( * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ -export interface BackendModuleConfig { +export interface CreateBackendModuleOptions { /** * Should exactly match the `id` of the plugin that the module extends. * @@ -175,7 +175,7 @@ export interface BackendModuleConfig { * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} */ export function createBackendModule( - config: BackendModuleConfig, + options: CreateBackendModuleOptions, ): () => BackendFeature { const factory: BackendFeatureFactory = () => { let registrations: InternalBackendModuleRegistration[]; @@ -192,7 +192,7 @@ export function createBackendModule( let init: InternalBackendModuleRegistration['init'] | undefined = undefined; - config.register({ + options.register({ registerExtensionPoint(ext, impl) { if (init) { throw new Error( @@ -214,15 +214,15 @@ export function createBackendModule( if (!init) { throw new Error( - `registerInit was not called by register in ${config.moduleId} module for ${config.pluginId}`, + `registerInit was not called by register in ${options.moduleId} module for ${options.pluginId}`, ); } registrations = [ { type: 'module', - pluginId: config.pluginId, - moduleId: config.moduleId, + pluginId: options.pluginId, + moduleId: options.moduleId, extensionPoints, init, }, diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 49f15c0b55..5197cec050 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -14,18 +14,44 @@ * limitations under the License. */ -export type { - BackendModuleConfig, - BackendPluginConfig, - ExtensionPointConfig, +import type { + CreateBackendPluginOptions, + CreateBackendModuleOptions, + CreateExtensionPointOptions, } from './factories'; + export { createBackendModule, createBackendPlugin, createExtensionPoint, } from './factories'; + export type { BackendModuleRegistrationPoints, BackendPluginRegistrationPoints, ExtensionPoint, } from './types'; + +export type { + CreateBackendPluginOptions, + CreateBackendModuleOptions, + CreateExtensionPointOptions, +}; + +/** + * @public + * @deprecated Use {@link CreateBackendPluginOptions} instead. + */ +export type BackendPluginConfig = CreateBackendPluginOptions; + +/** + * @public + * @deprecated Use {@link CreateBackendModuleOptions} instead. + */ +export type BackendModuleConfig = CreateBackendModuleOptions; + +/** + * @public + * @deprecated Use {@link CreateExtensionPointOptions} instead. + */ +export type ExtensionPointConfig = CreateExtensionPointOptions; From 56ebcfc1f30c498adf753cfcaa80f09a2948bb6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 13:04:10 +0000 Subject: [PATCH 545/567] chore(deps): update actions/checkout action to v4.1.6 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes.yml | 2 +- .github/workflows/automate_changeset_feedback.yml | 2 +- .github/workflows/automate_merge_message.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/deploy_docker-image.yml | 2 +- .github/workflows/deploy_microsite.yml | 2 +- .github/workflows/deploy_nightly.yml | 2 +- .github/workflows/deploy_packages.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_code-formatting.yml | 2 +- .github/workflows/sync_dependabot-changesets.yml | 2 +- .github/workflows/sync_release-manifest.yml | 4 ++-- .github/workflows/sync_renovate-changesets.yml | 2 +- .github/workflows/sync_snyk-github-issues.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/sync_version-packages.yml | 2 +- .github/workflows/uffizzi-build.yml | 4 ++-- .github/workflows/verify_accessibility.yml | 2 +- .github/workflows/verify_codeql.yml | 2 +- .github/workflows/verify_docs-quality.yml | 2 +- .github/workflows/verify_e2e-kubernetes.yml | 2 +- .github/workflows/verify_e2e-linux.yml | 2 +- .github/workflows/verify_e2e-techdocs.yml | 2 +- .github/workflows/verify_e2e-windows.yml | 2 +- .github/workflows/verify_fossa.yml | 2 +- .github/workflows/verify_microsite.yml | 2 +- .github/workflows/verify_microsite_accessibility.yml | 2 +- .github/workflows/verify_storybook.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- 29 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5f456a9b0b..0191d1f0af 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 6ac327bcd6..95b0a808a1 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 56ab3283ac..a53369c884 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd3b66ddaa..00181adb5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -68,7 +68,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -206,7 +206,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: fetch master branch run: git fetch origin master diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 19c58a8a60..ddf01fa49c 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -25,7 +25,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 2554e3eeb8..4a1feed0ce 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 6ec5b03d56..02a1c65681 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index cd2947a565..04badd65b0 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -74,7 +74,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -158,7 +158,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a51e3fefe8..8d5b2522af 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -34,7 +34,7 @@ jobs: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index dce7217201..bb2e00ffa6 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -14,7 +14,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index ddd60750d1..0102cf316a 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index c971be522f..1f58969202 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -21,7 +21,7 @@ jobs: run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1 - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: path: backstage # 'v' prefix is added here for the tag, we keep it out of the manifest logic @@ -29,7 +29,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: repository: backstage/versions path: backstage/versions diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index 9d031812ea..01e6ee6dfc 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 2ea3fcd5a1..cb5eaa1737 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index e6b274c395..4678c36907 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -29,7 +29,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master with: diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 884bcf3d95..a7f8d3674e 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 20000 fetch-tags: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 5ab208590a..6df4eeed4e 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: setup-node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -89,7 +89,7 @@ jobs: egress-policy: audit - name: Checkout git repo - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Render Compose File run: | # update image after the build above diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 75415f7961..996153f8d3 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -24,7 +24,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 0c30218d72..bcac7e947c 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -47,7 +47,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index f784608493..5b213f2c14 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 # Vale does not support file excludes, so we use the script to generate a list of files instead # The action also does not allow args or a local config file to be passed in, so the files array diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index 9763cbd085..77d2c3bdc1 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -26,7 +26,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index b9229263ae..f8583ac178 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -45,7 +45,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Configure Git run: | diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 31d9d8b06a..2ed566e36b 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -34,7 +34,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 064baf700e..e2a789d666 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -42,7 +42,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Configure Git run: | diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 152fe9f185..ba5d177cc4 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -19,7 +19,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 48d7da218d..256b00b160 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index 9728bf57c6..9986ae051e 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 9a59b7b071..1859c0b8bc 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 # Required to retrieve git history diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index cd7a9b62ae..5c4bed2d11 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -33,7 +33,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 From ad5612613f00d5ea256b4a5241ad08f6c06ac68a Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 27 May 2024 15:30:05 +0200 Subject: [PATCH 546/567] Update .changeset/warm-bees-hope.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Camila Belo --- .changeset/warm-bees-hope.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/warm-bees-hope.md b/.changeset/warm-bees-hope.md index 78b77cf5fb..2a3de7fda7 100644 --- a/.changeset/warm-bees-hope.md +++ b/.changeset/warm-bees-hope.md @@ -2,4 +2,4 @@ '@backstage/backend-plugin-api': patch --- -We renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` in order to standardize frontend and backend factories signatures. +Renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` to standardize frontend and backend factories signatures. From c0cd34912703c5ce757886bb79dbe41c150007cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 13:38:33 +0000 Subject: [PATCH 547/567] chore(deps): update dependency @changesets/cli to v2.27.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index ec0e25f926..ee0c67c9d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7835,9 +7835,9 @@ __metadata: languageName: node linkType: hard -"@changesets/apply-release-plan@npm:^7.0.0": - version: 7.0.0 - resolution: "@changesets/apply-release-plan@npm:7.0.0" +"@changesets/apply-release-plan@npm:^7.0.1": + version: 7.0.1 + resolution: "@changesets/apply-release-plan@npm:7.0.1" dependencies: "@babel/runtime": ^7.20.1 "@changesets/config": ^3.0.0 @@ -7852,7 +7852,7 @@ __metadata: prettier: ^2.7.1 resolve-from: ^5.0.0 semver: ^7.5.3 - checksum: ad83f89a3d46cd5249fa960cb0324114532bd5f25e74466d181afd6661273824859d038a12ba587a5e044f9169810e4a6febbb61e23c3819b3b28c00176a8bdf + checksum: 44a2686d3dc3ee569f23862a6c3da5c247987b320ddaf64be6c2096bb486b3da620c9336164f73e30e6272149e435988b46776508b81f328e1d66e885a8264cc languageName: node linkType: hard @@ -7894,11 +7894,11 @@ __metadata: linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.27.1 - resolution: "@changesets/cli@npm:2.27.1" + version: 2.27.3 + resolution: "@changesets/cli@npm:2.27.3" dependencies: "@babel/runtime": ^7.20.1 - "@changesets/apply-release-plan": ^7.0.0 + "@changesets/apply-release-plan": ^7.0.1 "@changesets/assemble-release-plan": ^6.0.0 "@changesets/changelog-git": ^0.2.0 "@changesets/config": ^3.0.0 @@ -7910,7 +7910,7 @@ __metadata: "@changesets/pre": ^2.0.0 "@changesets/read": ^0.6.0 "@changesets/types": ^6.0.0 - "@changesets/write": ^0.3.0 + "@changesets/write": ^0.3.1 "@manypkg/get-packages": ^1.1.3 "@types/semver": ^7.5.0 ansi-colors: ^4.1.3 @@ -7931,7 +7931,7 @@ __metadata: tty-table: ^4.1.5 bin: changeset: bin.js - checksum: 0d030dec7e0ef28626082a257d57f46cdf65edb65a95f5a3511a9d298ca052388d8ab7f9a714943864eddc59148c4afb0b802a9c75b5bea45aade4c0dc7a5fa6 + checksum: e3b0bb3a123f71701f3b76e80104968fdf99a7403b97860631ed6f98f7ba0df5d9fb56767191fa46148372a0b35d44f7883decb2b70f60d5fa2716692332c0fd languageName: node linkType: hard @@ -8071,16 +8071,16 @@ __metadata: languageName: node linkType: hard -"@changesets/write@npm:^0.3.0": - version: 0.3.0 - resolution: "@changesets/write@npm:0.3.0" +"@changesets/write@npm:^0.3.1": + version: 0.3.1 + resolution: "@changesets/write@npm:0.3.1" dependencies: "@babel/runtime": ^7.20.1 "@changesets/types": ^6.0.0 fs-extra: ^7.0.1 human-id: ^1.0.2 prettier: ^2.7.1 - checksum: 37588eb3ef2af15b3ea09d46864c994780619d20b791ea5b654801a035a3a12540c7f953e6e4f36731678615edc6d1c32f8fe174d599d3e6ce2d68263865788b + checksum: 6df0447e05ededbab71f36e6ad23aa77cf06eb6adda7a8b8e7fb9d6bd5bc93acceb916d55b2a37cb7e93fb05d39a236a0dd7ade5243aae4772885081101d4784 languageName: node linkType: hard From c87dea17c0fcc5c6b81606262ae070982201e316 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 May 2024 15:58:06 +0200 Subject: [PATCH 548/567] chore: fix formData should be undefined Signed-off-by: blam --- plugins/scaffolder-react/src/extensions/rjsf.ts | 2 +- .../components/fields/MultiEntityPicker/MultiEntityPicker.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-react/src/extensions/rjsf.ts b/plugins/scaffolder-react/src/extensions/rjsf.ts index b81f06758f..b90caabdb0 100644 --- a/plugins/scaffolder-react/src/extensions/rjsf.ts +++ b/plugins/scaffolder-react/src/extensions/rjsf.ts @@ -64,7 +64,7 @@ export interface ScaffolderRJSFFieldProps< /** The tree of unique ids for every child field */ idSchema: IdSchema; /** The data for this field */ - formData: T; + formData?: T; /** The tree of errors for this field and its children */ errorSchema?: ErrorSchema; /** The field change event handler; called with the updated form data and an optional `ErrorSchema` */ diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index f8fc831976..4ea26b4414 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -115,7 +115,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { } // We need to check against formData here as that's the previous value for this field. - if (formData.includes(ref) || allowArbitraryValues) { + if (formData?.includes(ref) || allowArbitraryValues) { return entityRef; } } @@ -173,7 +173,7 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { required={required} InputProps={{ ...params.InputProps, - required: formData.length === 0 && required, + required: formData?.length === 0 && required, }} /> )} From dfc389a04a57f5722c610c9b19071f4474e0892e Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 May 2024 16:06:29 +0200 Subject: [PATCH 549/567] chore: updating api-reports Signed-off-by: blam --- plugins/scaffolder-react/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-react/api-report.md b/plugins/scaffolder-react/api-report.md index 311f4116b3..850c30ced8 100644 --- a/plugins/scaffolder-react/api-report.md +++ b/plugins/scaffolder-react/api-report.md @@ -304,7 +304,7 @@ export interface ScaffolderRJSFFieldProps< disabled: boolean; errorSchema?: ErrorSchema; formContext?: F; - formData: T; + formData?: T; hideError?: boolean; idPrefix?: string; idSchema: IdSchema; From ac34e21771ed362ab81dadd12b29e2453f915dc4 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Mon, 27 May 2024 16:22:57 +0200 Subject: [PATCH 550/567] Update soft-flies-live.md Signed-off-by: Ben Lambert --- .changeset/soft-flies-live.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/soft-flies-live.md b/.changeset/soft-flies-live.md index 2de68f12ee..b331269647 100644 --- a/.changeset/soft-flies-live.md +++ b/.changeset/soft-flies-live.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend-module-gitlab': minor +'@backstage/plugin-scaffolder-backend-module-gitlab': patch --- -Add new Scaffolder action to trigger GitLab pipelines. +Add new `gitlab:pipeline:trigger` action to trigger GitLab pipelines. From e5049d33363b00e17de39932eaa9c1cff6e56fa3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 14:30:36 +0000 Subject: [PATCH 551/567] chore(deps): update dependency @types/lodash to v4.17.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ee0c67c9d6..79803dc908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17605,9 +17605,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151": - version: 4.17.1 - resolution: "@types/lodash@npm:4.17.1" - checksum: 01984d5b44c09ef45258f8ac6d0cf926900624064722d51a020ba179e5d4a293da0068fb278d87dc695586afe7ebd3362ec57f5c0e7c4f6c1fab9d04a80e77f5 + version: 4.17.4 + resolution: "@types/lodash@npm:4.17.4" + checksum: 268e652fd52d49189f155bc89b49bd4535aa44f0b6b0ed9ce7e50318307bda58147c49539d2047f39ca37cf5b5ea38dfb801d0dbcdbc8b019c95c1afc346b05a languageName: node linkType: hard From c1eeac9180fe8fd44eb9462c205b5b4a044352bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 14:31:38 +0000 Subject: [PATCH 552/567] chore(deps): update dependency lint-staged to v15.2.5 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 179 +++++++++++++++++++++++++++--------------------------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/yarn.lock b/yarn.lock index ee0c67c9d6..3b491e5899 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20986,12 +20986,12 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" +"braces@npm:^3.0.2, braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: ^7.0.1 - checksum: e2a8e769a863f3d4ee887b5fe21f63193a891c68b612ddb4b68d82d1b5f3ff9073af066c343e9867a393fe4c2555dcb33e89b937195feb9c1613d259edfcd459 + fill-range: ^7.1.1 + checksum: b95aa0b3bd909f6cd1720ffcf031aeaf46154dd88b4da01f9a1d3f7ea866a79eba76a6d01cbc3c422b2ee5cdc39a4f02491058d5df0d7bf6e6a162a832df1f69 languageName: node linkType: hard @@ -21547,13 +21547,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:5.3.0": - version: 5.3.0 - resolution: "chalk@npm:5.3.0" - checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 - languageName: node - linkType: hard - "chalk@npm:^3.0.0": version: 3.0.0 resolution: "chalk@npm:3.0.0" @@ -21564,6 +21557,13 @@ __metadata: languageName: node linkType: hard +"chalk@npm:~5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -22177,17 +22177,10 @@ __metadata: languageName: node linkType: hard -"commander@npm:*, commander@npm:^12.0.0": - version: 12.0.0 - resolution: "commander@npm:12.0.0" - checksum: bce9e243dc008baba6b8d923f95b251ad115e6e7551a15838d7568abebcca0fc832da1800cf37caf37852f35ce4b7fb794ba7a4824b88c5adb1395f9268642df - languageName: node - linkType: hard - -"commander@npm:11.1.0, commander@npm:^11.0.0": - version: 11.1.0 - resolution: "commander@npm:11.1.0" - checksum: fd1a8557c6b5b622c89ecdfde703242ab7db3b628ea5d1755784c79b8e7cb0d74d65b4a262289b533359cd58e1bfc0bf50245dfbcd2954682a6f367c828b79ef +"commander@npm:*, commander@npm:^12.0.0, commander@npm:~12.1.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 68e9818b00fc1ed9cdab9eb16905551c2b768a317ae69a5e3c43924c2b20ac9bb65b27e1cab36aeda7b6496376d4da908996ba2c0b5d79463e0fb1e77935d514 languageName: node linkType: hard @@ -22212,6 +22205,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^11.0.0": + version: 11.1.0 + resolution: "commander@npm:11.1.0" + checksum: fd1a8557c6b5b622c89ecdfde703242ab7db3b628ea5d1755784c79b8e7cb0d74d65b4a262289b533359cd58e1bfc0bf50245dfbcd2954682a6f367c828b79ef + languageName: node + linkType: hard + "commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.7.1": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -23526,7 +23526,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.3.4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:4.3.4, debug@npm:^4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -25828,23 +25828,6 @@ __metadata: languageName: unknown linkType: soft -"execa@npm:8.0.1": - version: 8.0.1 - resolution: "execa@npm:8.0.1" - dependencies: - cross-spawn: ^7.0.3 - get-stream: ^8.0.1 - human-signals: ^5.0.0 - is-stream: ^3.0.0 - merge-stream: ^2.0.0 - npm-run-path: ^5.1.0 - onetime: ^6.0.0 - signal-exit: ^4.1.0 - strip-final-newline: ^3.0.0 - checksum: cac1bf86589d1d9b73bdc5dda65c52012d1a9619c44c526891956745f7b366ca2603d29fe3f7460bacc2b48c6eab5d6a4f7afe0534b31473d3708d1265545e1f - languageName: node - linkType: hard - "execa@npm:^1.0.0": version: 1.0.0 resolution: "execa@npm:1.0.0" @@ -25877,6 +25860,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:~8.0.1": + version: 8.0.1 + resolution: "execa@npm:8.0.1" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^8.0.1 + human-signals: ^5.0.0 + is-stream: ^3.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^5.1.0 + onetime: ^6.0.0 + signal-exit: ^4.1.0 + strip-final-newline: ^3.0.0 + checksum: cac1bf86589d1d9b73bdc5dda65c52012d1a9619c44c526891956745f7b366ca2603d29fe3f7460bacc2b48c6eab5d6a4f7afe0534b31473d3708d1265545e1f + languageName: node + linkType: hard + "exit-hook@npm:^2.2.1": version: 2.2.1 resolution: "exit-hook@npm:2.2.1" @@ -26390,12 +26390,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: ^5.0.1 - checksum: cc283f4e65b504259e64fd969bcf4def4eb08d85565e906b7d36516e87819db52029a76b6363d0f02d0d532f0033c9603b9e2d943d56ee3b0d4f7ad3328ff917 + checksum: b4abfbca3839a3d55e4ae5ec62e131e2e356bf4859ce8480c64c4876100f4df292a63e5bb1618e1d7460282ca2b305653064f01654474aa35c68000980f17798 languageName: node linkType: hard @@ -31319,13 +31319,6 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:3.0.0": - version: 3.0.0 - resolution: "lilconfig@npm:3.0.0" - checksum: a155f1cd24d324ab20dd6974db9ebcf3fb6f2b60175f7c052d917ff8a746b590bc1ee550f6fc3cb1e8716c8b58304e22fe2193febebc0cf16fa86d85e6f896c5 - languageName: node - linkType: hard - "lilconfig@npm:^2.0.3": version: 2.1.0 resolution: "lilconfig@npm:2.1.0" @@ -31333,6 +31326,13 @@ __metadata: languageName: node linkType: hard +"lilconfig@npm:~3.1.1": + version: 3.1.1 + resolution: "lilconfig@npm:3.1.1" + checksum: dc8a4f4afde3f0fac6bd36163cc4777a577a90759b8ef1d0d766b19ccf121f723aa79924f32af5b954f3965268215e046d0f237c41c76e5ef01d4e6d1208a15e + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -31367,22 +31367,22 @@ __metadata: linkType: hard "lint-staged@npm:^15.0.0": - version: 15.2.2 - resolution: "lint-staged@npm:15.2.2" + version: 15.2.5 + resolution: "lint-staged@npm:15.2.5" dependencies: - chalk: 5.3.0 - commander: 11.1.0 - debug: 4.3.4 - execa: 8.0.1 - lilconfig: 3.0.0 - listr2: 8.0.1 - micromatch: 4.0.5 - pidtree: 0.6.0 - string-argv: 0.3.2 - yaml: 2.3.4 + chalk: ~5.3.0 + commander: ~12.1.0 + debug: ~4.3.4 + execa: ~8.0.1 + lilconfig: ~3.1.1 + listr2: ~8.2.1 + micromatch: ~4.0.7 + pidtree: ~0.6.0 + string-argv: ~0.3.2 + yaml: ~2.4.2 bin: lint-staged: bin/lint-staged.js - checksum: 031718ad3f839475fb1d41bda34bab4330f25814175808169daa2686ff026e5a667a25c95fdf3cd46dac72f9af2c98852565bb62d920992f5e2d3f730c279760 + checksum: 3025868d965eb401a5ebd903abd70cfebb8dbeb41eea1020c316f9c8c79083ea203f6cef95d32bfa8c9ec5486392b4ed08632ace6a5347b69cf238ba00e178f0 languageName: node linkType: hard @@ -31393,17 +31393,17 @@ __metadata: languageName: node linkType: hard -"listr2@npm:8.0.1": - version: 8.0.1 - resolution: "listr2@npm:8.0.1" +"listr2@npm:~8.2.1": + version: 8.2.1 + resolution: "listr2@npm:8.2.1" dependencies: cli-truncate: ^4.0.0 colorette: ^2.0.20 eventemitter3: ^5.0.1 log-update: ^6.0.0 - rfdc: ^1.3.0 + rfdc: ^1.3.1 wrap-ansi: ^9.0.0 - checksum: 4dfeabfa037b3981d0edbf30789971ba727ba4cfcc13051ceaff7a1b3d26509ef2d946015c65c600b0775ec9d1ef58a81937d94c9c03de464b654f429cc7c3ed + checksum: a37c032850fc01f45cf6144f2b66d0c56a596b708de1acbd52e7c396a2eb188d027ad132c93a0ad946d7932a581dfcfc2e4318bb301926b01877cb4903d09fbd languageName: node linkType: hard @@ -32894,7 +32894,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:4.0.5, micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": +"micromatch@npm:4.0.5": version: 4.0.5 resolution: "micromatch@npm:4.0.5" dependencies: @@ -32904,6 +32904,16 @@ __metadata: languageName: node linkType: hard +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:~4.0.7": + version: 4.0.7 + resolution: "micromatch@npm:4.0.7" + dependencies: + braces: ^3.0.3 + picomatch: ^2.3.1 + checksum: 3cde047d70ad80cf60c787b77198d680db3b8c25b23feb01de5e2652205d9c19f43bd81882f69a0fd1f0cde6a7a122d774998aad3271ddb1b8accf8a0f480cf7 + languageName: node + linkType: hard + "miller-rabin@npm:^4.0.0": version: 4.0.1 resolution: "miller-rabin@npm:4.0.1" @@ -35775,7 +35785,7 @@ __metadata: languageName: node linkType: hard -"pidtree@npm:0.6.0": +"pidtree@npm:~0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" bin: @@ -38672,10 +38682,10 @@ __metadata: languageName: node linkType: hard -"rfdc@npm:^1.3.0": - version: 1.3.0 - resolution: "rfdc@npm:1.3.0" - checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 +"rfdc@npm:^1.3.1": + version: 1.3.1 + resolution: "rfdc@npm:1.3.1" + checksum: d5d1e930aeac7e0e0a485f97db1356e388bdbeff34906d206fe524dd5ada76e95f186944d2e68307183fdc39a54928d4426bbb6734851692cfe9195efba58b79 languageName: node linkType: hard @@ -40451,7 +40461,7 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:0.3.2, string-argv@npm:~0.3.1": +"string-argv@npm:~0.3.1, string-argv@npm:~0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" checksum: 8703ad3f3db0b2641ed2adbb15cf24d3945070d9a751f9e74a924966db9f325ac755169007233e8985a39a6a292f14d4fee20482989b89b96e473c4221508a0f @@ -43976,13 +43986,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:2.3.4": - version: 2.3.4 - resolution: "yaml@npm:2.3.4" - checksum: e6d1dae1c6383bcc8ba11796eef3b8c02d5082911c6723efeeb5ba50fc8e881df18d645e64de68e421b577296000bea9c75d6d9097c2f6699da3ae0406c030d8 - languageName: node - linkType: hard - "yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": version: 1.10.2 resolution: "yaml@npm:1.10.2" @@ -43990,12 +43993,12 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3": - version: 2.4.1 - resolution: "yaml@npm:2.4.1" +"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.1.1, yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.3.2, yaml@npm:^2.3.3, yaml@npm:~2.4.2": + version: 2.4.2 + resolution: "yaml@npm:2.4.2" bin: yaml: bin.mjs - checksum: 4c391d07a5d5e935e058babb71026c9cdc9a6fd889e35dd91b53cfb0a12691b67c6c5c740858e71345fef18cd9c13c554a6dda9196f59820d769d94041badb0b + checksum: 90dda4485de04367251face9abb5c36927c94e44078f4e958e6468a07e74e7e92f89be20fc49860b6268c51ee5a5fc79ef89197d3f874bf24ef8921cc4ba9013 languageName: node linkType: hard From e17d4a384105faec9f2a5e44951ad624510a5e0d Mon Sep 17 00:00:00 2001 From: Alex Eftimie Date: Mon, 27 May 2024 16:51:24 +0200 Subject: [PATCH 553/567] Update .changeset/empty-tables-ring.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: Alex Eftimie --- .changeset/empty-tables-ring.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/empty-tables-ring.md b/.changeset/empty-tables-ring.md index 1b52801ae0..c83a987228 100644 --- a/.changeset/empty-tables-ring.md +++ b/.changeset/empty-tables-ring.md @@ -1,8 +1,8 @@ --- '@backstage/plugin-kubernetes-react': minor '@backstage/plugin-catalog-import': minor -'@backstage/plugin-kubernetes': minor -'@backstage/plugin-search': minor +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-search': patch --- Migrate from identityApi to fetchApi in frontend plugins. From 75dcd7e0a96b97ca91f6eb51d4ea51aab13e1086 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 27 May 2024 16:58:57 +0200 Subject: [PATCH 554/567] chore: added changeset Signed-off-by: blam --- .changeset/spicy-brooms-hang.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/spicy-brooms-hang.md diff --git a/.changeset/spicy-brooms-hang.md b/.changeset/spicy-brooms-hang.md new file mode 100644 index 0000000000..23f7da309b --- /dev/null +++ b/.changeset/spicy-brooms-hang.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Fixing bug in `formData` type as it should be `optional` as it's possibly undefined From 55e802ebbda7b277c9b2866279a859aa48283f3c Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 11:15:09 -0400 Subject: [PATCH 555/567] fix(openapi-tooling): breaking changes check Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5f456a9b0b..e2faa6cd37 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -39,7 +39,7 @@ jobs: - name: breaking changes check run: | - yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md + yarn backstage-repo-tools repo schema openapi diff --since origin/${{ github.base_ref }} > comment.md - name: Upload Rendered Comment as Artifact uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 From cb557001da59c1c4afff508f1bcb0a52b33207dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 15:16:20 +0000 Subject: [PATCH 556/567] chore(deps): update dependency nodemon to v3.1.1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 79803dc908..09c480ef59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34067,8 +34067,8 @@ __metadata: linkType: hard "nodemon@npm:^3.0.1": - version: 3.1.0 - resolution: "nodemon@npm:3.1.0" + version: 3.1.1 + resolution: "nodemon@npm:3.1.1" dependencies: chokidar: ^3.5.2 debug: ^4 @@ -34082,7 +34082,7 @@ __metadata: undefsafe: ^2.0.5 bin: nodemon: bin/nodemon.js - checksum: 0b721f66ee60d9bf092f6101965bc65769698fa2921d0283d90bbf3f0906aa4f3ac77316682375bd7f09c91679fddb131aa39f9fc839fea57061bbc8e81b60e3 + checksum: 43ed211d3a1eb267444265454c0dd306177fcef119c8c095b737d843648e7b51f10c033c262ec10a09df60aa2f237904fa659a96d0562541c55b87c3f5ba77ff languageName: node linkType: hard From 5c093f297d856189042feb7856a03a57fe1b182f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 20:39:11 -0400 Subject: [PATCH 557/567] fix(ci): workflow files aren't saving artifacts as expected Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 11 +++-------- .github/workflows/uffizzi-build.yml | 13 ++++--------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5f456a9b0b..0f51dc152f 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -45,14 +45,9 @@ jobs: uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec - path: comment.md + path: | + comment.md + ${{ github.event_path }} retention-days: 2 overwrite: true - - name: Upload PR Event as Artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - with: - name: preview-spec - path: ${{ github.event_path }} - retention-days: 2 - overwrite: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 5ab208590a..3542858273 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -97,18 +97,13 @@ jobs: kustomize edit set image backstage=${{ needs.build-backstage.outputs.tags }} kustomize build . > manifests.rendered.yml cat manifests.rendered.yml - - name: Upload Rendered Manifests File as Artifact + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec - path: ./.github/uffizzi/k8s/manifests/manifests.rendered.yml - retention-days: 2 - overwrite: true - - name: Upload PR Event as Artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - with: - name: preview-spec - path: ${{ github.event_path }} + path: | + ./.github/uffizzi/k8s/manifests/manifests.rendered.yml + ${{ github.event_path }} retention-days: 2 overwrite: true From 0413878007aec63331ff487712d44d51a03e6a97 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 20:48:29 -0400 Subject: [PATCH 558/567] workflows: adjust name of workflow Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 0f51dc152f..ddf8bde41d 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,7 +41,7 @@ jobs: run: | yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md - - name: Upload Rendered Comment as Artifact + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec From 50a608a7d90e0ac4633b0994664cd0bd68107fbc Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 21:12:24 -0400 Subject: [PATCH 559/567] workflows: clone events.json locally Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 6 +++++- .github/workflows/uffizzi-build.yml | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index ddf8bde41d..4a470290cd 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,13 +41,17 @@ jobs: run: | yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md + - name: clone github events.json to local path + run: | + cat ${{ github.event_path }} > events.json + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: | comment.md - ${{ github.event_path }} + events.json retention-days: 2 overwrite: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 3542858273..b981b46e0a 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -97,13 +97,18 @@ jobs: kustomize edit set image backstage=${{ needs.build-backstage.outputs.tags }} kustomize build . > manifests.rendered.yml cat manifests.rendered.yml + + - name: clone github events.json locally + run: | + cat ${{ github.event_path }} > events.json + - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: | ./.github/uffizzi/k8s/manifests/manifests.rendered.yml - ${{ github.event_path }} + events.json retention-days: 2 overwrite: true From 89e2ddf2158b2d7fcf591281d668952dbc97ba6b Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 22:29:18 -0400 Subject: [PATCH 560/567] workflow: stage all files into top level zip dir Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 6 +++--- .github/workflows/uffizzi-build.yml | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 4a470290cd..12fda469ee 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,9 +41,9 @@ jobs: run: | yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md - - name: clone github events.json to local path + - name: clone artifacts to current directory run: | - cat ${{ github.event_path }} > events.json + cat ${{ github.event_path }} > event.json - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 @@ -51,7 +51,7 @@ jobs: name: preview-spec path: | comment.md - events.json + event.json retention-days: 2 overwrite: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index b981b46e0a..95a4fa2e11 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -98,17 +98,18 @@ jobs: kustomize build . > manifests.rendered.yml cat manifests.rendered.yml - - name: clone github events.json locally + - name: clone artifacts into current directory run: | - cat ${{ github.event_path }} > events.json + cat ${{ github.event_path }} > event.json + cp ./.github/uffizzi/k8s/manifests/manifests.rendered.yml manifests.rendered.yml - name: Upload Artifacts uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: preview-spec path: | - ./.github/uffizzi/k8s/manifests/manifests.rendered.yml - events.json + manifests.rendered.yml + event.json retention-days: 2 overwrite: true From 18e6e7a14a65531009d0a019d6c4481948126797 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 27 May 2024 22:38:49 -0400 Subject: [PATCH 561/567] fix prettier Signed-off-by: aramissennyeydd --- .github/workflows/api-breaking-changes.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 12fda469ee..711842a02c 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -54,4 +54,3 @@ jobs: event.json retention-days: 2 overwrite: true - From 9b7aacf7c08df0308bf42e86c2751b039a356441 Mon Sep 17 00:00:00 2001 From: Dmitriy Lazarev Date: Tue, 28 May 2024 13:12:04 +0400 Subject: [PATCH 562/567] Add graphql-plugin to OSS plugins list Signed-off-by: Dmitriy Lazarev --- microsite/data/plugins/graphql-catalog.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 microsite/data/plugins/graphql-catalog.yaml diff --git a/microsite/data/plugins/graphql-catalog.yaml b/microsite/data/plugins/graphql-catalog.yaml new file mode 100644 index 0000000000..9255f475e7 --- /dev/null +++ b/microsite/data/plugins/graphql-catalog.yaml @@ -0,0 +1,14 @@ +--- +title: GraphQL Catalog +author: Frontside Software +authorUrl: https://frontside.com/ +category: Discovery +description: Adds the GraphQL Endpoint to Backstage Catalog as a plugin. +documentation: https://github.com/thefrontside/playhouse/blob/main/plugins/graphql-backend/README.md +iconUrl: https://raw.githubusercontent.com/thefrontside/frontside.com/production/legacy/src/img/frontside-logo.png +npmPackageName: '@frontside/backstage-plugin-graphql-backend' +tags: + - graphql + - catalog + - graphql-catalog +addedDate: '2024-05-01' From 73ca211b4b2fc0e51e726d23def5b3927e65a4ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 28 May 2024 13:28:14 +0200 Subject: [PATCH 563/567] Fix typo in 03-services.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jonas Müller --- docs/backend-system/architecture/03-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index 573f6612c2..59aa48e0eb 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -111,7 +111,7 @@ There are only two possible scopes for services, `'plugin'` and `'root'`. ## Root Scoped Services -If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factory for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. +If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factors for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it. From 6163500c38bfdb51ad9e49740e374a8b96ffd8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 28 May 2024 13:34:58 +0200 Subject: [PATCH 564/567] Fix typo in 03-services.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jonas Müller --- docs/backend-system/architecture/03-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index 59aa48e0eb..dbb316e5a1 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -111,7 +111,7 @@ There are only two possible scopes for services, `'plugin'` and `'root'`. ## Root Scoped Services -If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factors for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. +If a service is defined as a root scoped service, the implementation created by the factory will be shared across all plugins and services. One other differentiating factor for root scoped services is that they are always initialized, regardless of whether any plugins depend on them or not. This makes them suitable for implementing backend-wide concerns that are not specific to any individual plugin. There is a limitation in the usage of root scoped services, which is that their implementation can only depend on other root scoped services. Plugin scoped services on the other hand can depend on both root and plugin scoped services. Because of this limitation, one of the main reasons to define a root scoped services is to make it possible for other root scoped services to depend on it. From b6e5a34bfd5a6a27bf54e5c35ae43f5c771e04b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 28 May 2024 13:46:56 +0200 Subject: [PATCH 565/567] Fix typo in 03-services.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jonas Müller --- docs/backend-system/architecture/03-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index dbb316e5a1..93326eb0d9 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -157,7 +157,7 @@ export const fooServiceFactory = createServiceFactory({ }); ``` -Whatever value is returned by the `createRootContext` function will shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`. +Whatever value is returned by the `createRootContext` function will be shared and passed as the second argument to each invocation of the `factory` function. That way you can create a shared context that is used in the creation of each plugin instance. Unlike the `factory` function, the `createRootContext` function will only receive root scoped services as its dependencies, but just like the `factory` function, it can also be `async`. ## Default Service Factories From 61232f63de64406499383ee66708dd191c7b488e Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 28 May 2024 14:50:15 +0200 Subject: [PATCH 566/567] docs: fix proxy typo Signed-off-by: Vincenzo Scamporlino --- docs/tutorials/using-backstage-proxy-within-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index 3002eb1aff..1abfe9e5b6 100644 --- a/docs/tutorials/using-backstage-proxy-within-plugin.md +++ b/docs/tutorials/using-backstage-proxy-within-plugin.md @@ -125,7 +125,7 @@ export class MyAwesomeApiClient implements MyAwesomeApi { private async fetch(input: string, init?: RequestInit): Promise { // As configured previously for the backend proxy - const proxyUri = '${await this.discoveryApi.getBaseUrl('proxy')}/'; + const proxyUri = `${await this.discoveryApi.getBaseUrl('proxy')}/`; const resp = await fetch(`${proxyUri}${input}`, init); if (!resp.ok) throw new Error(resp); From 77da22e67fd95673f26f07ba717bae2d9de87dbc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 May 2024 13:43:01 +0000 Subject: [PATCH 567/567] Version Packages (next) --- .changeset/create-app-1716903719.md | 5 + .changeset/pre.json | 30 + docs/releases/v1.28.0-next.1-changelog.md | 1014 ++++++++++++ package.json | 2 +- packages/app-next/CHANGELOG.md | 16 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 16 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 15 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 14 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 16 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 19 + .../package.json | 2 +- packages/backend-legacy/CHANGELOG.md | 37 + packages/backend-legacy/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 9 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 9 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 16 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 34 + packages/backend/package.json | 2 +- packages/cli/CHANGELOG.md | 10 + packages/cli/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 + packages/create-app/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 10 + packages/repo-tools/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 8 + packages/techdocs-cli/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 10 + plugins/app-backend/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 24 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 8 + plugins/auth-node/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 11 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 10 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 11 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 13 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 14 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 11 + plugins/catalog-import/package.json | 2 +- plugins/catalog/CHANGELOG.md | 15 + plugins/catalog/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 10 + plugins/devtools-backend/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/events-backend/CHANGELOG.md | 9 + plugins/events-backend/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 9 + .../example-todo-list-backend/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 17 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 9 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-common/CHANGELOG.md | 6 + plugins/kubernetes-common/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 8 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 12 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 10 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 16 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 14 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 6 + plugins/notifications/package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 10 + plugins/permission-backend/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 10 + plugins/permission-node/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 8 + plugins/proxy-backend/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 28 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 10 + plugins/scaffolder-common/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 9 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 9 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 1413 +++++++++++++++++ plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 16 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 10 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 9 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 12 + plugins/search-backend/package.json | 2 +- plugins/search/CHANGELOG.md | 8 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 11 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 10 + plugins/signals-node/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 10 + plugins/techdocs-backend/package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 8 + plugins/techdocs-node/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 9 + plugins/user-settings-backend/package.json | 2 +- 178 files changed, 3552 insertions(+), 88 deletions(-) create mode 100644 .changeset/create-app-1716903719.md create mode 100644 docs/releases/v1.28.0-next.1-changelog.md diff --git a/.changeset/create-app-1716903719.md b/.changeset/create-app-1716903719.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1716903719.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/pre.json b/.changeset/pre.json index 7a0fc7ed6b..bbf21bc6a4 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -190,33 +190,63 @@ "calm-plums-wink", "cold-seas-end", "create-app-1716302437", + "create-app-1716903719", + "cyan-jobs-visit", "cyan-paws-beg", "cyan-snails-peel", "eighty-kings-dress", + "eighty-yaks-switch", "empty-spoons-tell", + "empty-tables-ring", + "famous-monkeys-count", + "forty-adults-roll", "four-adults-mix", + "friendly-keys-fold", "gentle-baboons-peel", + "gold-teachers-wink", + "great-cougars-guess", "itchy-spoons-cry", + "large-months-decide", "late-ants-impress", "late-students-live", + "little-cooks-approve", "loud-pumpkins-bow", "lovely-hats-pay", "lucky-taxis-rule", "many-moles-sing", "mean-laws-lay", + "neat-rivers-share", "new-numbers-hug", + "nice-pants-shave", + "nine-hairs-kick", "nine-ties-type", "old-trees-check", "olive-mangos-tickle", + "perfect-bikes-invite", + "polite-otters-talk", "rude-kings-press", "seven-geese-raise", + "shaggy-jokes-promise", + "six-llamas-give", "slimy-fans-raise", "smooth-gifts-nail", + "soft-flies-live", "sour-colts-juggle", + "spicy-brooms-hang", "spicy-camels-happen", + "spotty-plants-switch", + "strong-moose-work", + "stupid-tigers-bake", + "tall-lies-fetch", + "tall-pumas-teach", + "tender-seas-listen", + "thirty-plums-shout", "tiny-pandas-return", + "warm-bees-hope", + "weak-gifts-occur", "wet-crabs-guess", "wild-doors-cheat", + "wild-ears-walk", "wise-vans-sin", "wise-wasps-look", "young-camels-return" diff --git a/docs/releases/v1.28.0-next.1-changelog.md b/docs/releases/v1.28.0-next.1-changelog.md new file mode 100644 index 0000000000..cb5502738d --- /dev/null +++ b/docs/releases/v1.28.0-next.1-changelog.md @@ -0,0 +1,1014 @@ +# Release v1.28.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.28.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.28.0-next.1) + +## @backstage/backend-common@0.23.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/backend-defaults@0.3.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/backend-test-utils@0.4.0-next.1 + +### Minor Changes + +- 805cbe7: Added `TestCaches` that functions just like `TestDatabases` + +### Patch Changes + +- 9e63318: Made it possible to give access restrictions to `mockCredentials.service` +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.6.0-next.1 + +### Minor Changes + +- debcc8c: Migrate LDAP catalog module to the new backend system. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-import@0.12.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-kubernetes-backend@0.18.0-next.1 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-kubernetes-node@0.1.13-next.1 + +## @backstage/plugin-kubernetes-common@0.8.0-next.0 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +## @backstage/plugin-kubernetes-react@0.4.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + +## @backstage/plugin-notifications-backend@0.3.0-next.1 + +### Minor Changes + +- 07a789b: adding filtering of notifications by processors + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.1.0-next.1 + +### Minor Changes + +- 07a789b: add notification filters + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-notifications-node@0.2.0-next.1 + +### Minor Changes + +- 07a789b: add notifications filtering by processors + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @backstage/backend-app-api@0.7.6-next.1 + +### Patch Changes + +- 398b82a: Add support for JWKS tokens in ExternalTokenHandler. +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/backend-dynamic-feature-service@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/backend-plugin-api@0.6.19-next.1 + +### Patch Changes + +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- 0665b7e: Renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` to standardize frontend and backend factories signatures. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/backend-tasks@0.5.24-next.1 + +### Patch Changes + +- ed473cd: Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/cli@0.26.7-next.1 + +### Patch Changes + +- 788eca7: Fix readme for new plugins created using cli +- c00f7ee: Fix issue with `esm` loaded dependencies being different from the `cjs` import for Vite dependencies +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + +## @backstage/create-app@0.5.16-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/repo-tools@0.9.1-next.1 + +### Patch Changes + +- 8721a02: Add `--client-additional-properties` option to `openapi generate` command +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + +## @techdocs/cli@1.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + +## @backstage/plugin-app-backend@0.3.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend@0.22.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.14-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.12-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.12-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-auth-node@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-catalog@1.20.1-next.1 + +### Patch Changes + +- a2d2649: Export `catalogTranslationRef` under `/alpha` + +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-catalog-backend@1.23.0-next.1 + +### Patch Changes + +- d779e3b: Added a regex test to check commit hash. If url is from git commit branch ignore the edit url. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.2-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.1 + +### Patch Changes + +- 150fc77: Fixed an issue in `GitlabOrgDiscoveryEntityProvider` where a missing `orgEnabled` config key was throwing an error. +- f271164: Fixed an issue in `GitlabDiscoveryEntityProvider` where the fallback branch was taking precedence over the GitLab default branch. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + +## @backstage/plugin-devtools-backend@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + +## @backstage/plugin-events-backend@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-kubernetes@0.11.11-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-kubernetes-node@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + +## @backstage/plugin-notifications@0.2.2-next.1 + +### Patch Changes + +- 6d196b4: Fixes performance issue with Notifications title counter. + +## @backstage/plugin-permission-backend@0.5.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-permission-node@0.7.30-next.1 + +### Patch Changes + +- 9e63318: Ensure that service token access restrictions, when present, are taken into account +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## @backstage/plugin-proxy-backend@0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-scaffolder@1.20.2-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined + +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-scaffolder-backend@1.22.8-next.1 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + +### Patch Changes + +- 829e0ec: Add new `gitlab:pipeline:trigger` action to trigger GitLab pipelines. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.27-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-common@1.5.3-next.0 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +## @backstage/plugin-scaffolder-node@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.4.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + +## @backstage/plugin-scaffolder-react@1.8.7-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined +- 928cfa0: Fixed a typo ' + +## @backstage/plugin-search@1.4.12-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## @backstage/plugin-search-backend@1.5.10-next.1 + +### Patch Changes + +- 34dc47d: Move @backstage/repo-tools to devDependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + +## @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + +## @backstage/plugin-search-backend-node@1.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-signals-backend@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @backstage/plugin-signals-node@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + +## @backstage/plugin-techdocs-backend@1.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + +## @backstage/plugin-techdocs-node@1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + +## @backstage/plugin-user-settings-backend@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + +## example-app@0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## example-app-next@0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + +## example-backend@0.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-notifications-backend@0.3.0-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.16-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + +## example-backend-legacy@0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.28-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - example-app@0.2.98-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + +## @internal/plugin-todo-list-backend@1.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 diff --git a/package.json b/package.json index 7b7692623a..46041abf6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.28.0-next.0", + "version": "1.28.0-next.1", "private": true, "repository": { "type": "git", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 1bf9501f3b..4a58195198 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,21 @@ # example-app-next +## 0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.0.12-next.0 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 1921ebe3cb..3b1bf6a29a 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.12-next.0", + "version": "0.0.12-next.1", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 9d1606c9f4..01e12da7db 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,21 @@ # example-app +## 0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.26.7-next.1 + - @backstage/plugin-catalog-import@0.12.0-next.1 + - @backstage/plugin-kubernetes@0.11.11-next.1 + - @backstage/plugin-search@1.4.12-next.1 + - @backstage/plugin-notifications@0.2.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder@1.20.2-next.1 + - @backstage/plugin-catalog@1.20.1-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.12-next.1 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.2.98-next.0 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 013a3f7c41..c62624c770 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.98-next.0", + "version": "0.2.98-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 302c14d6f1..74d39824e0 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/backend-app-api +## 0.7.6-next.1 + +### Patch Changes + +- 398b82a: Add support for JWKS tokens in ExternalTokenHandler. +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.7.6-next.0 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 988dfd3db9..d2f21e4998 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.7.6-next.0", + "version": "0.7.6-next.1", "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 5d5920998b..62a9108d8a 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-common +## 0.23.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.22.1-next.0 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 564d9589bf..6a72699739 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.22.1-next.0", + "version": "0.23.0-next.1", "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 fe8952c64a..8019597e55 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/backend-defaults +## 0.3.0-next.1 + +### Minor Changes + +- 02103be: Deprecated and moved over core services to `@backstage/backend-defaults` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.2.19-next.0 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index f1eb7e0718..2e8c8cf757 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.2.19-next.0", + "version": "0.3.0-next.1", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 01b4447eb0..2f8bb9f71b 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/backend-dynamic-feature-service +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 34567a6c20..f9e4823f21 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.11-next.0", + "version": "0.2.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index 183b4a4954..0484874683 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,42 @@ # example-backend-legacy +## 0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-events-backend@0.3.6-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.20-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.36-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.28-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - example-app@0.2.98-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.2.99-next.0 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index cad1a50b7f..abdf3cb500 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.99-next.0", + "version": "0.2.99-next.1", "backstage": { "role": "backend" }, diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 74b0cfdab9..0676d76360 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-plugin-api +## 0.6.19-next.1 + +### Patch Changes + +- 9e63318: Added an optional `accessRestrictions` to external access service tokens and service principals in general, such that you can limit their access to certain plugins or permissions. +- 0665b7e: Renamed `BackendPluginConfig`, `BackendModuleConfig`, and `ExtensionPointConfig` respectively to `CreateBackendPluginOptions`, `CreateBackendModuleOptions`, and `CreateExtensionPointOptions` to standardize frontend and backend factories signatures. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.6.19-next.0 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index ec84d31a45..7fbd8d8b2f 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.6.19-next.0", + "version": "0.6.19-next.1", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 25290d5575..a8dd81ce25 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-tasks +## 0.5.24-next.1 + +### Patch Changes + +- ed473cd: Updated the `TaskScheduleDefinitionConfig` deprecated comment to point to `SchedulerServiceTaskScheduleDefinitionConfig` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.5.24-next.0 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index c57e334185..8a4de36426 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.24-next.0", + "version": "0.5.24-next.1", "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 aaf2cf4e27..c9a93857f7 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/backend-test-utils +## 0.4.0-next.1 + +### Minor Changes + +- 805cbe7: Added `TestCaches` that functions just like `TestDatabases` + +### Patch Changes + +- 9e63318: Made it possible to give access restrictions to `mockCredentials.service` +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.9-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index c84956e58a..dfc3ea82cc 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.3.9-next.0", + "version": "0.4.0-next.1", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 3b4104ece6..a8937ec456 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,39 @@ # example-backend +## 0.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-search-backend@1.5.10-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/plugin-kubernetes-backend@0.18.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend@1.22.8-next.1 + - @backstage/plugin-notifications-backend@0.3.0-next.1 + - @backstage/plugin-app-backend@0.3.68-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.5-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.37-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-devtools-backend@0.3.5-next.1 + - @backstage/plugin-permission-backend@0.5.43-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.16-next.0 + - @backstage/plugin-proxy-backend@0.5.0-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.25-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-signals-backend@0.1.5-next.1 + - @backstage/plugin-techdocs-backend@1.10.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.6-next.1 + ## 0.0.27-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 4ab3798633..999d4dcc46 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.27-next.0", + "version": "0.0.27-next.1", "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 f3b7d52c7e..3ad3bf3fe5 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/cli +## 0.26.7-next.1 + +### Patch Changes + +- 788eca7: Fix readme for new plugins created using cli +- c00f7ee: Fix issue with `esm` loaded dependencies being different from the `cjs` import for Vite dependencies +- Updated dependencies + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + ## 0.26.6-next.0 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 8479ff192e..e66202b92c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.26.6-next.0", + "version": "0.26.7-next.1", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 6fa1ef51c8..67560bf6c3 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/create-app +## 0.5.16-next.1 + +### Patch Changes + +- Bumped create-app version. + ## 0.5.16-next.0 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index d8bfb337fa..5f6566e78a 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.16-next.0", + "version": "0.5.16-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 4e4707d7f5..0f44f1ca88 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/repo-tools +## 0.9.1-next.1 + +### Patch Changes + +- 8721a02: Add `--client-additional-properties` option to `openapi generate` command +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/cli-node@0.2.6-next.0 + - @backstage/config-loader@1.8.0 + ## 0.9.1-next.0 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index dd0ba95ee7..fa6e99485a 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.9.1-next.0", + "version": "0.9.1-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index a154513749..6f72a73b79 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @techdocs/cli +## 1.8.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + ## 1.8.12-next.0 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 3edd15e192..8db3eace5e 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.12-next.0", + "version": "1.8.12-next.1", "publishConfig": { "access": "public" }, diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 77de6588f4..4be30962c0 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-app-backend +## 0.3.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.3.68-next.0 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 177b91a202..0b8bf97107 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.68-next.0", + "version": "0.3.68-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index 1da1830fab..ba07bcf852 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json index 3d2f4f890b..924b7323e9 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", "description": "The aws-alb provider module for the Backstage auth backend.", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md index e532415c6b..d3f83d4c6c 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.1.2-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index a332207890..67b1ad284b 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/package.json +++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider", - "version": "0.1.2-next.0", + "version": "0.1.2-next.1", "description": "The cloudflare-access-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index ae130e0fe2..4afdc14430 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 86276daea3..c29e5739f4 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "description": "The guest-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 191f459689..50d88f11b7 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend@0.22.6-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index cbdffb0f02..5b7701e96c 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", "description": "The oidc-provider backend module for the auth plugin.", - "version": "0.1.10-next.0", + "version": "0.1.10-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index fd910b9377..4f12db77b8 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-auth-backend +## 0.22.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.2-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.11-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.14-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.14-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.12-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.12-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.22.6-next.0 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index cccb390b5b..c3606041f6 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.6-next.0", + "version": "0.22.6-next.1", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin" diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index b60cf12574..5135ff8c91 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-node +## 0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.4.14-next.0 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 5fad67b237..4e6057f30e 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.4.14-next.0", + "version": "0.4.14-next.1", "backstage": { "role": "node-library" }, diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 13280d414a..0ec8426f1c 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 69d692a4cc..a36211ec13 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.3.14-next.0", + "version": "0.3.14-next.1", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 205c5a2b17..8b21c42d1f 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.39-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 221e0058eb..aa476f0ebc 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.1.39-next.0", + "version": "0.1.39-next.1", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index c02efbc491..2acbc2edc8 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.2.6-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 667bdf4a23..37d6978721 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.2.6-next.0", + "version": "0.2.6-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 534be4549d..d719c3ed22 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.33-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index d9e46ebec4..6b8bddb330 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.33-next.0", + "version": "0.1.33-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 3903d46108..e315bb0d4c 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.20-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index b832dfafab..2233d76705 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.1.20-next.0", + "version": "0.1.20-next.1", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 206c04b47c..8db092ca63 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.36-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 130ba40687..8a636884f2 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.36-next.0", + "version": "0.1.36-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 6a9793f161..213e9eb3f8 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.2-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.1.14-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index a6d2f7cfcc..f1dc9303d7 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.1.14-next.0", + "version": "0.1.14-next.1", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index b25241c192..3649676fef 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.6.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 4a53a7d389..1c7c110d7f 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.6.2-next.0", + "version": "0.6.2-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 58f9136ac5..8b309890eb 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.17-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.0.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 91972cd3df..e763b59b64 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.0.2-next.0", + "version": "0.0.2-next.1", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index d4fc84ed2a..11556923cc 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.17-next.1 + +### Patch Changes + +- 150fc77: Fixed an issue in `GitlabOrgDiscoveryEntityProvider` where a missing `orgEnabled` config key was throwing an error. +- f271164: Fixed an issue in `GitlabDiscoveryEntityProvider` where the fallback branch was taking precedence over the GitLab default branch. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.17-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index c4d4c07131..0d8379dc6c 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.3.17-next.0", + "version": "0.3.17-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 5051cf43db..a25cb9f75a 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.4.24-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index ab4bacf252..951c0d9f93 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.4.24-next.0", + "version": "0.4.24-next.1", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 6410c0bde3..a7a1efa77b 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.6.0-next.1 + +### Minor Changes + +- debcc8c: Migrate LDAP catalog module to the new backend system. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.5.35-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 8bfa0905f6..5c954561e9 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.5.35-next.0", + "version": "0.6.0-next.1", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 63b43005c7..7bbd4cf0fc 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.5.27-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index f031e85329..a2c579aaf2 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.5.27-next.0", + "version": "0.5.27-next.1", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index b02043655b..4c3fa26383 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-backend@1.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.37-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 7e4d72a5f1..5be17c2b69 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.1.37-next.0", + "version": "0.1.37-next.1", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 1fb2d5dc75..431d15bb64 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 43ec390541..a3eeaa5ee9 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.1.25-next.0", + "version": "0.1.25-next.1", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index 290f7acd35..5fe8cae147 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.1.17-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index f1842779e5..7f6109c823 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.1.17-next.0", + "version": "0.1.17-next.1", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 61d8ab845b..4111b25411 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + ## 0.4.6-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 5d78cde6ae..e01b636905 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.4.6-next.0", + "version": "0.4.6-next.1", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 22ca3b4f89..836d2282f3 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend +## 1.23.0-next.1 + +### Patch Changes + +- d779e3b: Added a regex test to check commit hash. If url is from git commit branch ignore the edit url. +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.25-next.1 + ## 1.23.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index a924881a4d..53bc27ecd6 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.23.0-next.0", + "version": "1.23.0-next.1", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin" diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index a72fb070da..407d437889 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-import +## 0.12.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.11.1-next.0 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 1aec15f541..8d6410bf76 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.11.1-next.0", + "version": "0.12.0-next.1", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 23d7fee635..c3623b8182 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog +## 1.20.1-next.1 + +### Patch Changes + +- a2d2649: Export `catalogTranslationRef` under `/alpha` +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.20.1-next.0 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 488bc05b4b..eefdcdf706 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.20.1-next.0", + "version": "1.20.1-next.1", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index d0c2f7d6fc..fd3e6d21bf 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-devtools-backend +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/config-loader@1.8.0 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 3b6ee4af31..bccd406ae0 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.5-next.0", + "version": "0.3.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 42ce92265e..47e43c4265 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 1a4951ae2f..3c1a796d5f 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.3.5-next.0", + "version": "0.3.5-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 84337dec34..0dff94a986 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.3.6-next.0 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 5bbcd48919..ae19b98d6e 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.6-next.0", + "version": "0.3.6-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 1350ca84c5..a380b2f707 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/plugin-todo-list-backend +## 1.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 1.0.28-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 60aa287efb..1c70aef305 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.28-next.0", + "version": "1.0.28-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 6b742d4f4d..98e3f983f3 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-kubernetes-backend +## 0.18.0-next.1 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-kubernetes-node@0.1.13-next.1 + ## 0.17.2-next.0 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 678e68816f..e3bf25bad2 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.17.2-next.0", + "version": "0.18.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index fe667ca054..1ed061ecb0 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.0.12-next.0 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 67527562a6..b1547ed24d 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.12-next.0", + "version": "0.0.12-next.1", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 778364af1b..7bf7741f3f 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-kubernetes-common +## 0.8.0-next.0 + +### Minor Changes + +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + ## 0.7.6 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index f3bba8b855..64807dd61f 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.7.6", + "version": "0.8.0-next.0", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index fd1481b0a1..d01ce7dc43 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes-node +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index fbc187750a..701106dfd0 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.13-next.0", + "version": "0.1.13-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library" diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 7d94edb692..ae1879f2b1 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-react +## 0.4.0-next.1 + +### Minor Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- 0177f75: Update kubernetes plugins to use autoscaling/v2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + ## 0.3.6-next.0 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 47f6cd0c25..71e5213d62 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-react", "description": "Web library for the kubernetes-react plugin", - "version": "0.3.6-next.0", + "version": "0.4.0-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index fcfbfdb6d2..77c059aa4f 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes +## 0.11.11-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-kubernetes-react@0.4.0-next.1 + - @backstage/plugin-kubernetes-common@0.8.0-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 0.11.11-next.0 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index bd47686e63..75ee785bf9 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.11-next.0", + "version": "0.11.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index e2b1e0cea4..dc4b0238e5 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-notifications-backend-module-email +## 0.1.0-next.1 + +### Minor Changes + +- 07a789b: add notification filters + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.0.2-next.0 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index d94048978e..21b47d9de4 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.0.2-next.0", + "version": "0.1.0-next.1", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index 923696afce..28929c9334 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-notifications-backend +## 0.3.0-next.1 + +### Minor Changes + +- 07a789b: adding filtering of notifications by processors + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 3d809ba11b..03e7214d85 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.2.2-next.0", + "version": "0.3.0-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index eca0358484..846b305f5e 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-notifications-node +## 0.2.0-next.1 + +### Minor Changes + +- 07a789b: add notifications filtering by processors + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 2500026f3b..b566da41f5 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.1.5-next.0", + "version": "0.2.0-next.1", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library" diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 08affa74d9..66a02ee34a 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-notifications +## 0.2.2-next.1 + +### Patch Changes + +- 6d196b4: Fixes performance issue with Notifications title counter. + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 6c975c8557..0e54a64f86 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.2-next.0", + "version": "0.2.2-next.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 46c70631c1..58db983a93 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-backend +## 0.5.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.5.43-next.0 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index c09007b817..33c95f7997 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.43-next.0", + "version": "0.5.43-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 756b93a37c..6a394c781d 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-node +## 0.7.30-next.1 + +### Patch Changes + +- 9e63318: Ensure that service token access restrictions, when present, are taken into account +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.7.30-next.0 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 254ef17d00..1878e4db08 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.30-next.0", + "version": "0.7.30-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 2db699508f..dac4e39607 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.5.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 0.5.0-next.0 ### Minor Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index af0d9d584e..59d9fb522d 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.5.0-next.0", + "version": "0.5.0-next.1", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index e152ffeba6..ba99c297e7 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.2.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 67b85ef18e..7839fa0dc0 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.20-next.0", + "version": "0.2.20-next.1", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index c3e2893dc6..291f711152 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.2.43-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 2d750a1086..6a1ab577aa 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.43-next.0", + "version": "0.2.43-next.1", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 373b06a638..099862e4b5 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.9-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.1.9-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 0d6baeaaa0..1f89cdc366 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.9-next.0", + "version": "0.1.9-next.1", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index a375e2202b..53091b92cb 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.2.9-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 6338947116..88eeb2860e 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.2.9-next.0", + "version": "0.2.9-next.1", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index f7d18d703a..3464ff436a 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.1-next.1 + +### Patch Changes + +- 829e0ec: Add new `gitlab:pipeline:trigger` action to trigger GitLab pipelines. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.4.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index b91b36bd55..bb7be8363b 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.4.1-next.0", + "version": "0.4.1-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 8e5994dd23..6c004bee97 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-notifications-node@0.2.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.0.2-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index fcebc0c511..c98eea2b8f 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.0.2-next.0", + "version": "0.0.2-next.1", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 1c174579bb..4afe60a1fe 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.4.36-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 62ba9ff77c..08b45f598c 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.36-next.0", + "version": "0.4.36-next.1", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 6f5d470dab..ca4532e8ce 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.27-next.1 + +### Patch Changes + +- 1354d81: Use `node-fetch` instead of native fetch, as per https://backstage.io/docs/architecture-decisions/adrs-adr013 +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.1.27-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 861e78b565..c5930717c6 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.27-next.0", + "version": "0.1.27-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 477470b8c9..9e087788a6 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-scaffolder-backend +## 1.22.8-next.1 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.9-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.1-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.17-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.9-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.11-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.2.9-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 1.22.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 0caa20c410..b1901e4fcd 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.22.8-next.0", + "version": "1.22.8-next.1", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 8b5c2d7c99..cbd9579ce9 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-common +## 1.5.3-next.0 + +### Patch Changes + +- bcec60f: added the following new permissions to the scaffolder backend endpoints: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + ## 1.5.2 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index a0060b1427..04f3375520 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.2", + "version": "1.5.3-next.0", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 770f78ef77..574d08d934 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.4.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.5-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index cd0cac649d..48f3ea22a7 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "backstage": { "role": "node-library" }, diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 0dc1ffeec8..579ded0816 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + ## 0.4.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 5f2834320f..900bf25b06 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.5-next.0", + "version": "0.4.5-next.1", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library" diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 84c442c040..c5327627bd 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,1418 @@ # @backstage/plugin-scaffolder-react +## 1.8.7-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined +- 928cfa0: Fixed a typo ' + +## 1.8.6-next.0 + +### Patch Changes + +- 86dc29d: Links that are rendered in the markdown in the `ScaffolderField` component are now opened in new tabs. +- Updated dependencies + - @backstage/theme@0.5.6-next.0 + - @backstage/core-components@0.14.8-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-react@1.12.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.2 + +## 1.8.5 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2 + - @backstage/core-components@0.14.7 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-react@1.12.0 + - @backstage/theme@0.5.4 + - @backstage/catalog-client@1.6.5 + +## 1.8.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.0-next.2 + - @backstage/core-components@0.14.7-next.2 + +## 1.8.5-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + +## 1.8.5-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.8.4 + +### Patch Changes + +- abfbcfc: Updated dependency `@testing-library/react` to `^15.0.0`. +- 87d2eb8: Updated dependency `json-schema-library` to `^9.0.0`. +- cb1e3b0: Updated dependency `@testing-library/dom` to `^10.0.0`. +- 0e692cf: Added ESLint rule `no-top-level-material-ui-4-imports` to migrate the Material UI imports. +- df99f62: The `value` sent on the `create` analytics event (fired when a Scaffolder template is executed) is now set to the number of minutes saved by executing the template. This value is derived from the `backstage.io/time-saved` annotation on the template entity, if available. + + Note: the `create` event is now captured in the `` component. If you are directly making use of the alpha-exported `` component, an analytics `create` event will no longer be captured on your behalf. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.11.3 + - @backstage/core-components@0.14.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/theme@0.5.3 + - @backstage/version-bridge@1.0.8 + - @backstage/catalog-client@1.6.4 + - @backstage/catalog-model@1.4.5 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.4-next.1 + +### Patch Changes + +- 87d2eb8: Updated dependency `json-schema-library` to `^9.0.0`. +- df99f62: The `value` sent on the `create` analytics event (fired when a Scaffolder template is executed) is now set to the number of minutes saved by executing the template. This value is derived from the `backstage.io/time-saved` annotation on the template entity, if available. + + Note: the `create` event is now captured in the `` component. If you are directly making use of the alpha-exported `` component, an analytics `create` event will no longer be captured on your behalf. + +- Updated dependencies + - @backstage/catalog-client@1.6.4-next.0 + - @backstage/catalog-model@1.4.5 + - @backstage/core-components@0.14.4-next.0 + - @backstage/core-plugin-api@1.9.1 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.11.3-next.1 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.4-next.0 + - @backstage/catalog-client@1.6.3 + - @backstage/catalog-model@1.4.5 + - @backstage/core-plugin-api@1.9.1 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.11.3-next.0 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.3 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/catalog-client@1.6.3 + - @backstage/core-components@0.14.3 + - @backstage/plugin-catalog-react@1.11.2 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.2 + +### Patch Changes + +- e8f026a: Use ESM exports of react-use library +- Updated dependencies + - @backstage/catalog-client@1.6.2 + - @backstage/core-components@0.14.2 + - @backstage/plugin-catalog-react@1.11.1 + - @backstage/core-plugin-api@1.9.1 + - @backstage/catalog-model@1.4.5 + - @backstage/theme@0.5.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.1 + +### Patch Changes + +- 930b5c1: Added 'root' and 'label' class key to TemplateCategoryPicker +- 6d649d2: Updated dependency `flatted` to `3.3.1`. +- 0cecb09: Updated dependency `@rjsf/utils` to `5.17.1`. + Updated dependency `@rjsf/core` to `5.17.1`. + Updated dependency `@rjsf/material-ui` to `5.17.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.1`. +- Updated dependencies + - @backstage/core-components@0.14.1 + - @backstage/theme@0.5.2 + - @backstage/plugin-catalog-react@1.11.0 + - @backstage/catalog-client@1.6.1 + - @backstage/catalog-model@1.4.5 + - @backstage/core-plugin-api@1.9.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1 + +## 1.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.1-next.2 + - @backstage/plugin-catalog-react@1.11.0-next.2 + - @backstage/catalog-client@1.6.1-next.1 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/theme@0.5.2-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1-next.1 + +## 1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.1-next.1 + - @backstage/plugin-catalog-react@1.10.1-next.1 + - @backstage/core-plugin-api@1.9.1-next.1 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/theme@0.5.2-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1-next.1 + +## 1.8.1-next.0 + +### Patch Changes + +- 930b5c1: Added 'root' and 'label' class key to TemplateCategoryPicker +- 6d649d2: Updated dependency `flatted` to `3.3.1`. +- 0cecb09: Updated dependency `@rjsf/utils` to `5.17.1`. + Updated dependency `@rjsf/core` to `5.17.1`. + Updated dependency `@rjsf/material-ui` to `5.17.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.1`. +- Updated dependencies + - @backstage/theme@0.5.2-next.0 + - @backstage/core-components@0.14.1-next.0 + - @backstage/plugin-catalog-react@1.10.1-next.0 + - @backstage/catalog-client@1.6.1-next.0 + - @backstage/catalog-model@1.4.5-next.0 + - @backstage/core-plugin-api@1.9.1-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.1-next.0 + +## 1.8.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- 11b9a08: Introduced the first version of recoverable tasks. +- b07ec70: Use more distinguishable icons for link (`Link`) and text output (`Description`). + +### Patch Changes + +- 3f60ad5: fix for: converting circular structure to JSON error +- 0b0c6b6: Allow defining default output text to be shown +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- 31f0a0a: Added `ScaffolderPageContextMenu` to `ActionsPage`, `ListTaskPage`, and `TemplateEditorPage` so that you can more easily navigate between these pages +- 09cedb9: Updated dependency `@react-hookz/web` to `^24.0.0`. +- e6f0831: Updated dependency `@rjsf/utils` to `5.17.0`. + Updated dependency `@rjsf/core` to `5.17.0`. + Updated dependency `@rjsf/material-ui` to `5.17.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.0`. +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- 3dff4b0: Remove unused deps +- 82affc7: Fix issue where `ui:schema` was replaced with an empty object if `dependencies` is defined +- 2985186: Fix bug that erroneously caused a separator or a 0 to render in the TemplateCard for Templates with empty links +- Updated dependencies + - @backstage/plugin-catalog-react@1.10.0 + - @backstage/core-components@0.14.0 + - @backstage/catalog-model@1.4.4 + - @backstage/theme@0.5.1 + - @backstage/core-plugin-api@1.9.0 + - @backstage/catalog-client@1.6.0 + - @backstage/plugin-scaffolder-common@1.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.0-next.3 + +### Patch Changes + +- 09cedb9: Updated dependency `@react-hookz/web` to `^24.0.0`. +- e6f0831: Updated dependency `@rjsf/utils` to `5.17.0`. + Updated dependency `@rjsf/core` to `5.17.0`. + Updated dependency `@rjsf/material-ui` to `5.17.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.17.0`. +- Updated dependencies + - @backstage/theme@0.5.1-next.1 + - @backstage/core-components@0.14.0-next.2 + - @backstage/plugin-catalog-react@1.10.0-next.3 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.0-next.1 + +## 1.8.0-next.2 + +### Patch Changes + +- 8fe56a8: Widen `@types/react` dependency range to include version 18. +- 2985186: Fix bug that erroneously caused a separator or a 0 to render in the TemplateCard for Templates with empty links +- Updated dependencies + - @backstage/core-components@0.14.0-next.1 + - @backstage/core-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-react@1.10.0-next.2 + - @backstage/theme@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.0-next.1 + +## 1.8.0-next.1 + +### Minor Changes + +- b07ec70: Use more distinguishable icons for link (`Link`) and text output (`Description`). + +### Patch Changes + +- 3f60ad5: fix for: converting circular structure to JSON error +- 31f0a0a: Added `ScaffolderPageContextMenu` to `ActionsPage`, `ListTaskPage`, and `TemplateEditorPage` so that you can more easily navigate between these pages +- 82affc7: Fix issue where `ui:schema` was replaced with an empty object if `dependencies` is defined +- Updated dependencies + - @backstage/core-components@0.14.0-next.0 + - @backstage/catalog-model@1.4.4-next.0 + - @backstage/catalog-client@1.6.0-next.1 + - @backstage/core-plugin-api@1.8.3-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.5.0-next.1 + +## 1.8.0-next.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- 0b0c6b6: Allow defining default output text to be shown +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.7.1 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 0b9ce2b: Fix for a step with no properties +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- d16f85f: Show first scaffolder output text by default +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.7.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 1.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.7.1-next.0 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.7.0 + +### Minor Changes + +- 33edf50: Added support for dealing with user provided secrets using a new field extension `ui:field: Secret` + +### Patch Changes + +- 670c7cc: Fix bug where `properties` is set to empty object when it should be empty for schema dependencies +- fa66d1b: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- e516bf4: Step titles in the Stepper are now clickable and redirect the user to the corresponding step, as an alternative to using the back buttons. +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- 2aee53b: Add horizontal slider if stepper overflows +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.6.2-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.2 + +### Patch Changes + +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.1 + +### Patch Changes + +- fa66d1b5b3: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- 2aee53bbeb: Add horizontal slider if stepper overflows +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.0 + +### Patch Changes + +- e516bf4da8: Step titles in the Stepper are now clickable and redirect the user to the corresponding step, as an alternative to using the back buttons. +- aaa6fb3bc9: Minor updates for TypeScript 5.2.2+ compatibility +- 6cd12f277b: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- 63c494ef22: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.0 + - @backstage/plugin-catalog-react@1.9.2-next.0 + - @backstage/core-components@0.13.9-next.0 + - @backstage/theme@0.5.0-next.0 + - @backstage/catalog-client@1.4.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.0 + +### Minor Changes + +- 3fdffbb699: Release design improvements for the `Scaffolder` plugin and support v5 of `@rjsf/*` libraries. + + This change should be non-breaking. If you're seeing typescript issues after migrating please [open an issue](https://github.com/backstage/backstage/issues/new/choose) + + The `next` versions like `createNextFieldExtension` and `NextScaffolderPage` have been promoted to the public interface under `createScaffolderFieldExtension` and `ScaffolderPage`, so any older imports which are no longer found will need updating from `@backstage/plugin-scaffolder/alpha` or `@backstage/plugin-scaffolder-react/alpha` will need to be imported from `@backstage/plugin-scaffolder` and `@backstage/plugin-scaffolder-react` respectively. + + The legacy versions are now available in `/alpha` under `createLegacyFieldExtension` and `LegacyScaffolderPage` if you're running into issues, but be aware that these will be removed in a next mainline release. + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- 171a99816b: Fixed `backstage:featureFlag` in `scaffolder/next` by sorting out `manifest.steps`. +- c838da0edd: Updated dependency `@rjsf/utils` to `5.13.6`. + Updated dependency `@rjsf/core` to `5.13.6`. + Updated dependency `@rjsf/material-ui` to `5.13.6`. + Updated dependency `@rjsf/validator-ajv8` to `5.13.6`. +- 69c14904b6: Use `EntityRefLinks` with `hideIcons` property to avoid double icons +- 62b5922916: Internal theme type updates +- dda56ae265: Preserve step's time execution for a non-running task. +- 76d07da66a: Make it possible to define control buttons text (Back, Create, Review) per template +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.0 + - @backstage/core-components@0.13.8 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/core-plugin-api@1.8.0 + - @backstage/version-bridge@1.0.7 + - @backstage/theme@0.4.4 + - @backstage/catalog-client@1.4.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.8-next.2 + - @backstage/plugin-catalog-react@1.9.0-next.2 + +## 1.6.0-next.1 + +### Patch Changes + +- 62b5922916: Internal theme type updates +- 76d07da66a: Make it possible to define control buttons text (Back, Create, Review) per template +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.0-next.1 + - @backstage/plugin-scaffolder-common@1.4.3-next.1 + - @backstage/core-components@0.13.8-next.1 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/theme@0.4.4-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7-next.0 + +## 1.6.0-next.0 + +### Minor Changes + +- 3fdffbb699: Release design improvements for the `Scaffolder` plugin and support v5 of `@rjsf/*` libraries. + + This change should be non-breaking. If you're seeing typescript issues after migrating please [open an issue](https://github.com/backstage/backstage/issues/new/choose) + + The `next` versions like `createNextFieldExtension` and `NextScaffolderPage` have been promoted to the public interface under `createScaffolderFieldExtension` and `ScaffolderPage`, so any older imports which are no longer found will need updating from `@backstage/plugin-scaffolder/alpha` or `@backstage/plugin-scaffolder-react/alpha` will need to be imported from `@backstage/plugin-scaffolder` and `@backstage/plugin-scaffolder-react` respectively. + + The legacy versions are now available in `/alpha` under `createLegacyFieldExtension` and `LegacyScaffolderPage` if you're running into issues, but be aware that these will be removed in a next mainline release. + +### Patch Changes + +- 6c2b872153: Add official support for React 18. +- Updated dependencies + - @backstage/core-components@0.13.7-next.0 + - @backstage/plugin-scaffolder-common@1.4.3-next.0 + - @backstage/plugin-catalog-react@1.9.0-next.0 + - @backstage/core-plugin-api@1.8.0-next.0 + - @backstage/version-bridge@1.0.7-next.0 + - @backstage/theme@0.4.4-next.0 + - @backstage/catalog-client@1.4.5 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.5.6 + +### Patch Changes + +- 9a1fce352e: Updated dependency `@testing-library/jest-dom` to `^6.0.0`. +- f95af4e540: Updated dependency `@testing-library/dom` to `^9.0.0`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.5 + - @backstage/core-plugin-api@1.7.0 + - @backstage/core-components@0.13.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/version-bridge@1.0.6 + - @backstage/theme@0.4.3 + - @backstage/catalog-client@1.4.5 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.2 + +## 1.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.6-next.2 + - @backstage/core-plugin-api@1.7.0-next.1 + - @backstage/catalog-model@1.4.3-next.0 + - @backstage/plugin-catalog-react@1.8.5-next.2 + - @backstage/errors@1.2.3-next.0 + - @backstage/theme@0.4.3-next.0 + - @backstage/catalog-client@1.4.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + - @backstage/plugin-scaffolder-common@1.4.2-next.0 + +## 1.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.6-next.1 + - @backstage/plugin-catalog-react@1.8.5-next.1 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/theme@0.4.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + - @backstage/plugin-scaffolder-common@1.4.1 + +## 1.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.5-next.0 + - @backstage/core-plugin-api@1.7.0-next.0 + - @backstage/core-components@0.13.6-next.0 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/errors@1.2.2 + - @backstage/theme@0.4.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + - @backstage/plugin-scaffolder-common@1.4.1 + +## 1.5.5 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- b16c341ced45: Updated dependency `@rjsf/utils` to `5.13.0`. + Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.13.0`. + Updated dependency `@rjsf/material-ui-v5` to `npm:@rjsf/material-ui@5.13.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.13.0`. +- 27fef07f9229: Updated dependency `use-immer` to `^0.9.0`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.4 + - @backstage/core-components@0.13.5 + - @backstage/catalog-client@1.4.4 + - @backstage/catalog-model@1.4.2 + - @backstage/core-plugin-api@1.6.0 + - @backstage/errors@1.2.2 + - @backstage/plugin-scaffolder-common@1.4.1 + - @backstage/theme@0.4.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.5 + +## 1.5.5-next.3 + +### Patch Changes + +- 406b786a2a2c: Mark package as being free of side effects, allowing more optimized Webpack builds. +- b16c341ced45: Updated dependency `@rjsf/utils` to `5.13.0`. + Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.13.0`. + Updated dependency `@rjsf/material-ui-v5` to `npm:@rjsf/material-ui@5.13.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.13.0`. +- Updated dependencies + - @backstage/catalog-client@1.4.4-next.2 + - @backstage/catalog-model@1.4.2-next.2 + - @backstage/core-components@0.13.5-next.3 + - @backstage/core-plugin-api@1.6.0-next.3 + - @backstage/errors@1.2.2-next.0 + - @backstage/plugin-catalog-react@1.8.4-next.3 + - @backstage/plugin-scaffolder-common@1.4.1-next.2 + - @backstage/theme@0.4.2-next.0 + - @backstage/types@1.1.1-next.0 + - @backstage/version-bridge@1.0.5-next.0 + +## 1.5.5-next.2 + +### Patch Changes + +- 27fef07f9229: Updated dependency `use-immer` to `^0.9.0`. +- Updated dependencies + - @backstage/core-components@0.13.5-next.2 + - @backstage/core-plugin-api@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.8.4-next.2 + - @backstage/catalog-model@1.4.2-next.1 + - @backstage/catalog-client@1.4.4-next.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.4.1-next.1 + +## 1.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.4-next.1 + - @backstage/core-components@0.13.5-next.1 + - @backstage/catalog-model@1.4.2-next.0 + - @backstage/core-plugin-api@1.6.0-next.1 + - @backstage/catalog-client@1.4.4-next.0 + - @backstage/plugin-scaffolder-common@1.4.1-next.0 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.6.0-next.0 + - @backstage/core-components@0.13.5-next.0 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-catalog-react@1.8.3-next.0 + - @backstage/plugin-scaffolder-common@1.4.0 + +## 1.5.2 + +### Patch Changes + +- ba9ee98a37bd: Fixed bug in Workflow component by passing a prop `templateName` down to Stepper component. +- Updated dependencies + - @backstage/core-components@0.13.4 + - @backstage/plugin-catalog-react@1.8.1 + - @backstage/plugin-scaffolder-common@1.4.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + +## 1.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.1-next.1 + +## 1.5.2-next.0 + +### Patch Changes + +- ba9ee98a37bd: Fixed bug in Workflow component by passing a prop `templateName` down to Stepper component. +- Updated dependencies + - @backstage/core-components@0.13.4-next.0 + - @backstage/core-plugin-api@1.5.3 + - @backstage/plugin-catalog-react@1.8.1-next.0 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/errors@1.2.1 + - @backstage/theme@0.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.2 + +## 1.5.1 + +### Patch Changes + +- f74a27de4d2c: Made markdown description theme-able +- Updated dependencies + - @backstage/theme@0.4.1 + - @backstage/errors@1.2.1 + - @backstage/plugin-catalog-react@1.8.0 + - @backstage/core-components@0.13.3 + - @backstage/core-plugin-api@1.5.3 + - @backstage/catalog-client@1.4.3 + - @backstage/catalog-model@1.4.1 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.2 + +## 1.5.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.8.0-next.2 + - @backstage/theme@0.4.1-next.1 + - @backstage/core-plugin-api@1.5.3-next.1 + - @backstage/core-components@0.13.3-next.2 + - @backstage/catalog-client@1.4.3-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/errors@1.2.1-next.0 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.2-next.0 + +## 1.5.1-next.1 + +### Patch Changes + +- f74a27de4d2c: Made markdown description theme-able +- Updated dependencies + - @backstage/theme@0.4.1-next.0 + - @backstage/core-components@0.13.3-next.1 + - @backstage/core-plugin-api@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.7.1-next.1 + +## 1.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.1-next.0 + - @backstage/core-components@0.13.3-next.0 + - @backstage/catalog-client@1.4.3-next.0 + - @backstage/catalog-model@1.4.1-next.0 + - @backstage/core-plugin-api@1.5.2 + - @backstage/theme@0.4.0 + - @backstage/types@1.1.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-catalog-react@1.7.1-next.0 + - @backstage/plugin-scaffolder-common@1.3.2-next.0 + +## 1.5.0 + +### Minor Changes + +- 6b571405f806: `scaffolder/next`: Provide some default template components to `rjsf` to allow for standardization and markdown descriptions +- 4505dc3b4598: `scaffolder/next`: Don't render `TemplateGroups` when there's no results in with search query +- a452bda74d7a: Fixed typescript casting bug for useTemplateParameterSchema hook +- 6b571405f806: `scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from Material UI, making it easier to write `FieldExtensions`. + +### Patch Changes + +- 84a5c7724c7e: fixed refresh problem when backstage backend disconnects without any feedback to user. Now we send a generic message and try to reconnect after 15 seconds +- cf34311cdbe1: Extract `ui:*` fields from conditional `then` and `else` schema branches. +- 2ff94da135a4: bump `rjsf` dependencies to 5.7.3 +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/core-plugin-api@1.5.2 + - @backstage/catalog-client@1.4.2 + - @backstage/core-components@0.13.2 + - @backstage/types@1.1.0 + - @backstage/theme@0.4.0 + - @backstage/plugin-catalog-react@1.7.0 + - @backstage/catalog-model@1.4.0 + - @backstage/errors@1.2.0 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.1 + +## 1.5.0-next.3 + +### Minor Changes + +- a452bda74d7a: Fixed typescript casting bug for useTemplateParameterSchema hook + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.2-next.3 + - @backstage/catalog-model@1.4.0-next.1 + - @backstage/catalog-client@1.4.2-next.2 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/errors@1.2.0-next.0 + - @backstage/theme@0.4.0-next.1 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-catalog-react@1.7.0-next.3 + - @backstage/plugin-scaffolder-common@1.3.1-next.1 + +## 1.5.0-next.2 + +### Patch Changes + +- cf34311cdbe1: Extract `ui:*` fields from conditional `then` and `else` schema branches. +- 2ff94da135a4: bump `rjsf` dependencies to 5.7.3 +- Updated dependencies + - @backstage/theme@0.4.0-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.2 + - @backstage/core-components@0.13.2-next.2 + - @backstage/core-plugin-api@1.5.2-next.0 + +## 1.5.0-next.1 + +### Minor Changes + +- 6b571405f806: `scaffolder/next`: Provide some default template components to `rjsf` to allow for standardization and markdown descriptions +- 4505dc3b4598: `scaffolder/next`: Don't render `TemplateGroups` when there's no results in with search query +- 6b571405f806: `scaffolder/next`: provide a `ScaffolderField` component which is meant to replace some of the `FormControl` components from Material UI, making it easier to write `FieldExtensions`. + +### Patch Changes + +- 74b216ee4e50: Add `PropsWithChildren` to usages of `ComponentType`, in preparation for React 18 where the children are no longer implicit. +- Updated dependencies + - @backstage/errors@1.2.0-next.0 + - @backstage/core-components@0.13.2-next.1 + - @backstage/plugin-catalog-react@1.7.0-next.1 + - @backstage/catalog-model@1.4.0-next.0 + - @backstage/core-plugin-api@1.5.2-next.0 + - @backstage/catalog-client@1.4.2-next.1 + - @backstage/plugin-scaffolder-common@1.3.1-next.0 + - @backstage/theme@0.4.0-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.4.1-next.0 + +### Patch Changes + +- 84a5c7724c7e: fixed refresh problem when backstage backend disconnects without any feedback to user. Now we send a generic message and try to reconnect after 15 seconds +- Updated dependencies + - @backstage/catalog-client@1.4.2-next.0 + - @backstage/plugin-catalog-react@1.7.0-next.0 + - @backstage/theme@0.4.0-next.0 + - @backstage/core-components@0.13.2-next.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-model@1.3.0 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.3.0 + +## 1.4.0 + +### Minor Changes + +- 82e10a6939c: Add support for Markdown text blob outputs from templates + +### Patch Changes + +- ad1a1429de4: Improvements to the `scaffolder/next` buttons UX: + + - Added padding around the "Create" button in the `Stepper` component + - Added a button bar that includes the "Cancel" and "Start Over" buttons to the `OngoingTask` component. The state of these buttons match their existing counter parts in the Context Menu + - Added a "Show Button Bar"/"Hide Button Bar" item to the `ContextMenu` component + +- Updated dependencies + - @backstage/theme@0.3.0 + - @backstage/plugin-catalog-react@1.6.0 + - @backstage/plugin-scaffolder-common@1.3.0 + - @backstage/core-components@0.13.1 + - @backstage/catalog-client@1.4.1 + - @backstage/catalog-model@1.3.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + +## 1.4.0-next.2 + +### Minor Changes + +- 82e10a6939c: Add support for Markdown text blob outputs from templates + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.3.0-next.0 + - @backstage/plugin-scaffolder-common@1.3.0-next.0 + - @backstage/core-components@0.13.1-next.1 + - @backstage/plugin-catalog-react@1.6.0-next.2 + - @backstage/core-plugin-api@1.5.1 + +## 1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.1-next.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/plugin-catalog-react@1.6.0-next.1 + +## 1.3.1-next.0 + +### Patch Changes + +- ad1a1429de4: Improvements to the `scaffolder/next` buttons UX: + + - Added padding around the "Create" button in the `Stepper` component + - Added a button bar that includes the "Cancel" and "Start Over" buttons to the `OngoingTask` component. The state of these buttons match their existing counter parts in the Context Menu + - Added a "Show Button Bar"/"Hide Button Bar" item to the `ContextMenu` component + +- Updated dependencies + - @backstage/plugin-catalog-react@1.6.0-next.0 + - @backstage/core-components@0.13.0 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-client@1.4.1 + - @backstage/catalog-model@1.3.0 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.19 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4 + - @backstage/plugin-scaffolder-common@1.2.7 + +## 1.3.0 + +### Minor Changes + +- 259d3407b9b: Move `CategoryPicker` from `scaffolder` into `scaffolder-react` + Move `ContextMenu` into `scaffolder-react` and rename it to `ScaffolderPageContextMenu` +- 2cfd03d7376: To offer better customization options, `ScaffolderPageContextMenu` takes callbacks as props instead of booleans +- 48da4c46e45: `scaffolder/next`: Export the `TemplateGroupFilter` and `TemplateGroups` and make an extensible component + +### Patch Changes + +- 7e1d900413a: `scaffolder/next`: Bump `@rjsf/*` dependencies to 5.5.2 +- e27ddc36dad: Added a possibility to cancel the running task (executing of a scaffolder template) +- 0435174b06f: Accessibility issues identified using lighthouse fixed. +- 7a6b16cc506: `scaffolder/next`: Bump `@rjsf/*` deps to 5.3.1 +- 90dda42cfd2: bug: Invert `templateFilter` predicate to align with `Array.filter` +- d2488f5e54c: Add an indication that the validators are running when clicking `next` on each step of the form. +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- 8c40997df44: Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.5.2`. +- f84fc7fd040: Updated dependency `@rjsf/validator-ajv8` to `5.3.0`. +- 8e00acb28db: Small tweaks to remove warnings in the console during development (mainly focusing on techdocs) +- 34dab7ee7f8: `scaffolder/next`: bump `rjsf` dependencies to `5.5.0` +- 2898b6c8d52: Minor type tweaks for TypeScript 5.0 +- e0c6e8b9c3c: Update peer dependencies +- cf71c3744a5: scaffolder/next: Bump `@rjsf/*` dependencies to 5.6.0 +- Updated dependencies + - @backstage/core-components@0.13.0 + - @backstage/plugin-scaffolder-common@1.2.7 + - @backstage/catalog-client@1.4.1 + - @backstage/plugin-catalog-react@1.5.0 + - @backstage/theme@0.2.19 + - @backstage/core-plugin-api@1.5.1 + - @backstage/catalog-model@1.3.0 + - @backstage/version-bridge@1.0.4 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.3.0-next.3 + +### Patch Changes + +- d2488f5e54c: Add indication that the validators are running +- 8c40997df44: Updated dependency `@rjsf/core-v5` to `npm:@rjsf/core@5.5.2`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.5.0-next.3 + - @backstage/catalog-model@1.3.0-next.0 + - @backstage/core-components@0.13.0-next.3 + - @backstage/catalog-client@1.4.1-next.1 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.19-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/plugin-scaffolder-common@1.2.7-next.2 + +## 1.3.0-next.2 + +### Patch Changes + +- 90dda42cfd2: bug: Invert `templateFilter` predicate to align with `Array.filter` +- 34dab7ee7f8: `scaffolder/next`: bump `rjsf` dependencies to `5.5.0` +- 2898b6c8d52: Minor type tweaks for TypeScript 5.0 +- Updated dependencies + - @backstage/catalog-client@1.4.1-next.0 + - @backstage/core-components@0.12.6-next.2 + - @backstage/plugin-catalog-react@1.4.1-next.2 + - @backstage/core-plugin-api@1.5.1-next.1 + - @backstage/catalog-model@1.2.1 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.19-next.0 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/plugin-scaffolder-common@1.2.7-next.1 + +## 1.3.0-next.1 + +### Patch Changes + +- 1e4f5e91b8e: Bump `zod` and `zod-to-json-schema` dependencies. +- e0c6e8b9c3c: Update peer dependencies +- Updated dependencies + - @backstage/core-components@0.12.6-next.1 + - @backstage/plugin-scaffolder-common@1.2.7-next.1 + - @backstage/core-plugin-api@1.5.1-next.0 + - @backstage/version-bridge@1.0.4-next.0 + - @backstage/plugin-catalog-react@1.4.1-next.1 + - @backstage/theme@0.2.19-next.0 + - @backstage/catalog-client@1.4.0 + - @backstage/catalog-model@1.2.1 + - @backstage/errors@1.1.5 + - @backstage/types@1.0.2 + +## 1.3.0-next.0 + +### Minor Changes + +- 259d3407b9b: Move `CategoryPicker` from `scaffolder` into `scaffolder-react` + Move `ContextMenu` into `scaffolder-react` and rename it to `ScaffolderPageContextMenu` +- 2cfd03d7376: To offer better customization options, `ScaffolderPageContextMenu` takes callbacks as props instead of booleans +- 48da4c46e45: `scaffolder/next`: Export the `TemplateGroupFilter` and `TemplateGroups` and make an extensible component + +### Patch Changes + +- e27ddc36dad: Added a possibility to cancel the running task (executing of a scaffolder template) +- 7a6b16cc506: `scaffolder/next`: Bump `@rjsf/*` deps to 5.3.1 +- f84fc7fd040: Updated dependency `@rjsf/validator-ajv8` to `5.3.0`. +- 8e00acb28db: Small tweaks to remove warnings in the console during development (mainly focusing on techdocs) +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.2.7-next.0 + - @backstage/core-components@0.12.6-next.0 + - @backstage/plugin-catalog-react@1.4.1-next.0 + - @backstage/core-plugin-api@1.5.0 + - @backstage/catalog-client@1.4.0 + - @backstage/catalog-model@1.2.1 + - @backstage/errors@1.1.5 + - @backstage/theme@0.2.18 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.2.0 + +### Minor Changes + +- 8f4d13f21cf: Move `useTaskStream`, `TaskBorder`, `TaskLogStream` and `TaskSteps` into `scaffolder-react`. + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- 3c96e77b513: Make scaffolder adhere to page themes by using page `fontColor` consistently. If your theme overwrites template list or card headers, review those styles. +- c8d78b9ae9d: fix bug with `hasErrors` returning false when dealing with empty objects +- 9b8c374ace5: Remove timer for skipped steps in Scaffolder Next's TaskSteps +- 44941fc97eb: scaffolder/next: Move the `uiSchema` to its own property in the validation `context` to align with component development and access of `ui:options` +- d9893263ba9: scaffolder/next: Fix for steps without properties +- 928a12a9b3e: Internal refactor of `/alpha` exports. +- cc418d652a7: scaffolder/next: Added the ability to get the fields definition in the schema in the validation function +- d4100d0ec42: Fix alignment bug for owners on `TemplateCard` +- Updated dependencies + - @backstage/catalog-client@1.4.0 + - @backstage/core-components@0.12.5 + - @backstage/plugin-catalog-react@1.4.0 + - @backstage/errors@1.1.5 + - @backstage/core-plugin-api@1.5.0 + - @backstage/catalog-model@1.2.1 + - @backstage/theme@0.2.18 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.6 + +## 1.2.0-next.2 + +### Patch Changes + +- 65454876fb2: Minor API report tweaks +- 3c96e77b513: Make scaffolder adhere to page themes by using page `fontColor` consistently. If your theme overwrites template list or card headers, review those styles. +- d9893263ba9: scaffolder/next: Fix for steps without properties +- Updated dependencies + - @backstage/core-components@0.12.5-next.2 + - @backstage/plugin-catalog-react@1.4.0-next.2 + - @backstage/core-plugin-api@1.5.0-next.2 + +## 1.2.0-next.1 + +### Minor Changes + +- 8f4d13f21cf: Move `useTaskStream`, `TaskBorder`, `TaskLogStream` and `TaskSteps` into `scaffolder-react`. + +### Patch Changes + +- 44941fc97eb: scaffolder/next: Move the `uiSchema` to its own property in the validation `context` to align with component development and access of `ui:options` +- Updated dependencies + - @backstage/core-components@0.12.5-next.1 + - @backstage/errors@1.1.5-next.0 + - @backstage/catalog-client@1.4.0-next.1 + - @backstage/core-plugin-api@1.4.1-next.1 + - @backstage/theme@0.2.18-next.0 + - @backstage/plugin-catalog-react@1.4.0-next.1 + - @backstage/catalog-model@1.2.1-next.1 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.6-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- c8d78b9ae9: fix bug with `hasErrors` returning false when dealing with empty objects +- 928a12a9b3: Internal refactor of `/alpha` exports. +- cc418d652a: scaffolder/next: Added the ability to get the fields definition in the schema in the validation function +- d4100d0ec4: Fix alignment bug for owners on `TemplateCard` +- Updated dependencies + - @backstage/catalog-client@1.4.0-next.0 + - @backstage/plugin-catalog-react@1.4.0-next.0 + - @backstage/core-plugin-api@1.4.1-next.0 + - @backstage/catalog-model@1.2.1-next.0 + - @backstage/core-components@0.12.5-next.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.17 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.6-next.0 + +## 1.1.0 + +### Minor Changes + +- a07750745b: Added `DescriptionField` field override to the `next/scaffolder` +- a521379688: Migrating the `TemplateEditorPage` to work with the new components from `@backstage/plugin-scaffolder-react` +- 8c2966536b: Embed scaffolder workflow in other components +- 5555e17313: refactor `createAsyncValidators` to be recursive to ensure validators are called in nested schemas. + +### Patch Changes + +- 04f717a8e1: `scaffolder/next`: bump `react-jsonschema-form` libraries to `v5-stable` +- b46f385eff: scaffolder/next: Implementing a simple `OngoingTask` page +- cbab8ac107: lock versions of `@rjsf/*-beta` packages +- 346d6b6630: Upgrade `@rjsf` version 5 dependencies to `beta.18` +- ccbf91051b: bump `@rjsf` `v5` dependencies to 5.1.0 +- d2ddde2108: Add `ScaffolderLayouts` to `NextScaffolderPage` +- Updated dependencies + - @backstage/core-components@0.12.4 + - @backstage/catalog-model@1.2.0 + - @backstage/theme@0.2.17 + - @backstage/core-plugin-api@1.4.0 + - @backstage/plugin-catalog-react@1.3.0 + - @backstage/catalog-client@1.3.1 + - @backstage/errors@1.1.4 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.5 + +## 1.1.0-next.2 + +### Minor Changes + +- 5555e17313: refactor `createAsyncValidators` to be recursive to ensure validators are called in nested schemas. + +### Patch Changes + +- b46f385eff: scaffolder/next: Implementing a simple `OngoingTask` page +- ccbf91051b: bump `@rjsf` `v5` dependencies to 5.1.0 +- Updated dependencies + - @backstage/catalog-model@1.2.0-next.1 + - @backstage/core-components@0.12.4-next.1 + - @backstage/catalog-client@1.3.1-next.1 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-catalog-react@1.3.0-next.2 + - @backstage/plugin-scaffolder-common@1.2.5-next.1 + +## 1.1.0-next.1 + +### Patch Changes + +- 04f717a8e1: `scaffolder/next`: bump `react-jsonschema-form` libraries to `v5-stable` +- 346d6b6630: Upgrade `@rjsf` version 5 dependencies to `beta.18` +- Updated dependencies + - @backstage/core-components@0.12.4-next.0 + - @backstage/plugin-catalog-react@1.3.0-next.1 + - @backstage/catalog-client@1.3.1-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.5-next.0 + +## 1.1.0-next.0 + +### Minor Changes + +- 8c2966536b: Embed scaffolder workflow in other components + +### Patch Changes + +- cbab8ac107: lock versions of `@rjsf/*-beta` packages +- d2ddde2108: Add `ScaffolderLayouts` to `NextScaffolderPage` +- Updated dependencies + - @backstage/plugin-catalog-react@1.3.0-next.0 + - @backstage/catalog-model@1.1.6-next.0 + - @backstage/catalog-client@1.3.1-next.0 + - @backstage/plugin-scaffolder-common@1.2.5-next.0 + +## 1.0.0 + +### Major Changes + +- b4955ed7b9: Re-home some of the common types, components, hooks and `scaffolderApiRef` for the `@backstage/plugin-scaffolder` to this package for easy re-use across things that want to interact with the `scaffolder`. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.5 + - @backstage/plugin-scaffolder-common@1.2.4 + - @backstage/catalog-client@1.3.0 + - @backstage/plugin-catalog-react@1.2.4 + - @backstage/core-components@0.12.3 + - @backstage/core-plugin-api@1.3.0 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + +## 1.0.0-next.0 + +### Major Changes + +- b4955ed7b9: Re-home some of the common types, components, hooks and `scaffolderApiRef` for the `@backstage/plugin-scaffolder` to this package for easy re-use across things that want to interact with the `scaffolder`. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.3.0-next.1 + - @backstage/catalog-client@1.3.0-next.2 + - @backstage/plugin-catalog-react@1.2.4-next.2 + - @backstage/catalog-model@1.1.5-next.1 + - @backstage/core-components@0.12.3-next.2 + - @backstage/errors@1.1.4 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.2 + - @backstage/version-bridge@1.0.3 + - @backstage/plugin-scaffolder-common@1.2.4-next.1 + in the review step label +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.8.6-next.0 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 0256f47d48..4460dfef50 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.8.6-next.0", + "version": "1.8.7-next.1", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library" diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 120d239ea1..5ba2e05768 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-scaffolder +## 1.20.2-next.1 + +### Patch Changes + +- 75dcd7e: Fixing bug in `formData` type as it should be `optional` as it's possibly undefined +- bcec60f: updated the ContextMenu, ActionsPage, OngoingTask and TemplateCard frontend components to support the new scaffolder permissions: + + - `scaffolder.task.create` + - `scaffolder.task.cancel` + - `scaffolder.task.read` + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.7-next.1 + - @backstage/plugin-scaffolder-common@1.5.3-next.0 + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.20.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9236784abd..1dbe7ad7b0 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.20.1-next.0", + "version": "1.20.2-next.1", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin" diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 3cd732f2b9..4c47c9d55a 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-search-backend-module-catalog +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 41828a7475..b1b1e0d643 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.25-next.0", + "version": "0.1.25-next.1", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 73aa7aee2c..228f390ddc 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 1.4.2-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 888b89e1bc..90ac189516 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.4.2-next.0", + "version": "1.4.2-next.1", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index 27137acc34..0649e48fcb 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-explore +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 3e32174a2a..8abc027d57 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.1.25-next.0", + "version": "0.1.25-next.1", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 9d4fd62f82..2344912758 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.6-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.5.28-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 899abcee31..e19cdc7559 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.28-next.0", + "version": "0.5.28-next.1", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index 1d643c7180..e7f5cd560b 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 0.1.12-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index da22880943..e21c1c6a18 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.1.12-next.0", + "version": "0.1.12-next.1", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index bd810f5208..9a05345cd8 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-catalog-node@1.12.1-next.0 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + ## 0.1.24-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 5df953cbf0..1b81340671 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.24-next.0", + "version": "0.1.24-next.1", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 769f8e5785..928a406cf9 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-node +## 1.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.24-next.1 + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 1.2.24-next.0 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 50f59089b2..f5ce41e840 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.2.24-next.0", + "version": "1.2.24-next.1", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index c116515a7a..fa17e485c8 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend +## 1.5.10-next.1 + +### Patch Changes + +- 34dc47d: Move @backstage/repo-tools to devDependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/plugin-permission-node@0.7.30-next.1 + - @backstage/backend-defaults@0.3.0-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-node@1.2.24-next.1 + ## 1.5.10-next.0 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 40371682fc..095c2d368d 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.10-next.0", + "version": "1.5.10-next.1", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin" diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 98c9852acc..e3455c41ab 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search +## 1.4.12-next.1 + +### Patch Changes + +- 4f92394: Migrate from identityApi to fetchApi in frontend plugins. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.1-next.0 + ## 1.4.12-next.0 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index c8a46d9555..1014036287 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.12-next.0", + "version": "1.4.12-next.1", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin" diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index e748bcd493..25b77a2261 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-signals-backend +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + - @backstage/plugin-signals-node@0.1.5-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 8a2ec3373b..5b87a03929 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index ab160c0c8f..a596027808 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-signals-node +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + - @backstage/plugin-events-node@0.3.5-next.0 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index f93947ae4b..d1e130d01b 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-signals-node", "description": "Node.js library for the signals plugin", - "version": "0.1.5-next.0", + "version": "0.1.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 2764510f3c..cec699fcd7 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-techdocs-backend +## 1.10.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.24-next.1 + - @backstage/plugin-techdocs-node@1.12.5-next.1 + ## 1.10.6-next.0 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index df8cd6e096..f96c6ec31c 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.6-next.0", + "version": "1.10.6-next.1", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin" diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index c7d0223508..00ad945353 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-techdocs-node +## 1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + ## 1.12.5-next.0 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 08b52b45b0..95ef28a2e0 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.5-next.0", + "version": "1.12.5-next.1", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library" diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 9402f0d688..915f283428 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-user-settings-backend +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.19-next.1 + - @backstage/backend-common@0.23.0-next.1 + - @backstage/plugin-auth-node@0.4.14-next.1 + ## 0.2.18-next.0 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index b3210d8357..38d6d0e04d 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.2.18-next.0", + "version": "0.2.18-next.1", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin"

    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

7LHg# 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 LNkvXXu0mjfloK)$ literal 0 HcmV?d00001 diff --git a/docs/assets/software-templates/template-editor-load-dir.png b/docs/assets/software-templates/template-editor-load-dir.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb22ab47c870d01798f992f725fbe314b8c216b GIT binary patch literal 597409 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%0A~ku6#a8P!&X+ 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�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%0AU*5! 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